现在的位置: 首页 > 综合 > 正文

声明与定义&&赋值与初始化详解

2013年10月29日 ⁄ 综合 ⁄ 共 8000字 ⁄ 字号 评论关闭

        原文地址:http://blog.csdn.net/zlhy_/article/details/8442573

       (修改了一些如代码重复的一些小问题)

 

我想不管是现在学C系语言的人,还是学习Java等以及其他语言的人最初的入门语言应该都是C语言吧。C语言的难易程度是否适合作为编程入门语言我恐怕没有这个能量来论述一番。我想说的是C语言中的这几个名词(RT)自从最开始出现就一直萦绕在耳边,停留在口头上,但却又总是分不清,道不明的。最近几天搜集了一些这方面的文章与帖子来总结一下,总结是一项很好的学习方法,如果你能把你所学的东西说给别人听,并且把别人说懂了,那么你就算掌握呃。如果博客对各位看官有一丝一毫的用处,那将是我最大的荣耀与欣喜了。

一:变量的声明与定义

严格的来说变量的声明是向编译器说明一个变量,这个行为是不分配内存空间的,例如:extern int ivalue;指明ivalue是别处的一个已经定义好的变量,在这里我只是使用一下,所以要向编译器先声明。声明也可以去掉类型名:extern ivalue;

但是这仅仅是狭义的声明,我们在写程序的过程中用到的更多的是广义的声明,这是与定义有着联系的。广义的声明分为两种,如下

1引用性声明:

extern int ivalue;这个例子就算是引用性声明,就是前面所述的狭义的声明。这种声明是可以出现在程序中的多处地方的。

2定义性声明:

这也是我们在平时用的比较多的,而这种声明往往也被称作是定义了。所以定义是声明的一种,这种是分配内存空间的。

int ivalue;//因为语言的性质决定了我们在使用一个变量前要先声明,这也算是一种声明,向编译器说明一个整型变量,但是编译器会为这个变量分配内存空间的,换句话说:在声明变量的同时也为变量分配了存储空间,也就是定义了。因为定义的实质就是分配内存空间。全局变量的定义只能在一处进行,否则编译器会抛出多重定义的错误。这里不多说全局变量与局部变量之间的覆盖问题。

还有一点,带有extern关键字的也可能是定义,如extern int ivalue = 11;有了这句,后面的代码中可以出现extern int ivalue;但是不能再出现类似int ivalue; int ivalue = 11;这样子的定义语句了。否则会抛错:重定义


二:赋值与初始化

上面说了定义是要为变量分配内存空间的,如int ival;但是编译器仅仅负责分配一个4字节(在32bits下)的空间用于存储ival这个变量的值。如果变量的定义没有初始化,那这个最初分配的空间的值是随机的(空间上原来存储的值)。不经过初始化的变量可能会为程序带来不安全与不确定性。所以Scott Meyers这样子的大师也建议大家最好为你的所有的变量初始化,哪怕是内置基本类型。

初始化如果根据不同的类型来分,则可分为内置类型初始化与自定义类型初始化。

内置类型的初始化:

1直接初始化:

int ival(11);这种写法可能在初学C语言的时候不常见到,但是这确实是正确的语法,并且是直接初始化。类似于调用了构造函数,但是对于int这种内置类型是没有构造函数的,但是别忘了站在C++对象模型的角度,int也算是对象类型的。(<深度探索C++对象模型>一书中有介绍)。

2复制初始化:

int ival = 11;这种形式大概是初学者所接触的最多的了,C语言的书上也很多是这种。值得一提的是,这虽然是一个等号,但这不是赋值行为,而是初始化行为。

3:除了以上两条细致的区别之外,内置类型是否初始化还要依照于变量的定义位置与变量的声明(定义)形式。

》在函数体之外的non-static变量,也就是全局变量,如int ival。并没有手动进行初始化,编译器将其存储在常量非初始化区,编译器友好的帮我们默默的做了初始化的工作了。默认值是该类型的“零值”,那int类型的就是0了,float类型的就是0.0f了,char类型的就是''(空字符),string类型的就是""(空字符串);bool类型的为“假”记为false;
//关于零值,参看林锐博士的<高质量C++/C编程指南>。在函数体内的non-static变量没有编译器的优待。

》上面提及到的变量都是non-static限制,那是仅仅因为定义的位置不同而有着不同的“待遇”。还有因变量的声明(定义)方式不同:不管在函数体内还是在函数体外,变量前用static修饰后,如static int ivalue;虽然没有进行显式的初始化,但是编译器将其存储于静态非初始化区,而且编译器会为没初始化的static变量默默的初始化为该类型的“零值”。

》这些未经过初始化(不管是显式的还是编译器默认做的)的变量除非作为赋值运算符的左操作数,否则在程序中的其余使用均是未定义行为,这种行为在程序中不易发现,而且往往会给给程序带来极大的不便,永远不要依赖于未定义行为。

PS:未初始化的变量事实上都有一个值。编译器把该变量放到内存中的某个位置,而把这个位置的无论哪种位模式都当成是变量初始的状态,当被解释成整型值时,任何位模式都是合法的值,虽然这个值不可能是程序员想要的。因为这个值合法,所以使用它也不可能会导致程序崩溃,可能的结果是导致程序错误执行和/或错误计算。


赋值:

是在编译器为变量分配内存空间的动作发生之后的某个时刻里,变量的值被刷新行为(也就是存储空间被改写)。还有就是能够在赋值运算符左侧的一定是左值(左值可以等价于内存空间来理解)。

int ival;

ival = 11; //符合赋值操作


int ival1;

ival1 = ival; //符合赋值操作


类对象的赋值与初始化:


MyClass cls(10); //调用构造函数初始化对象

MyClass cls1(cls); //调用复制构造函数初始化对象

MyClass cls2 = cls; //这里仍然是初始化行为,并且调用的是复制构造函数,而不是那个赋值运算符重载函数。

cls2 = cls1; //这句代码是赋值行为,同时调用的是赋值运算符重载函数。参看笔者另一篇博客:点击打开链接


还有一种很特殊的变量,那就是类对象中的成员变量,他们的初始化与赋值操作往往是很迷惑的。代码解释如下:

 

可能会有人和我一样最开始先入为主了,总觉得构造函数不就是用来初始化的吗?难道构造函数做的不是初始化的工作吗?构造函数做的当然是初始化的工作了,可构造函数对于这个类对象来说是做的初始化工作,这是毋庸置疑的。

但是我们这里的问题是我们怎么初始化类中的这些成员变量呢?反正上面的那个构造函数Student(...);对于这些成员变量来说做的不是初始化工作,而是赋值行为了,为什么呢?

我们知道const变量是必须要初始化的,试想一下类中的const变量是怎么初始化的呢?C++标准强制规定,使用参数初始化列表来进行初始化工作。假如类中还有一个const变量string c_Sex(c记为常量的意思);声明在三个变量的后面。那这个变量就不能和上面的三个变量放在一起了,必须这样子做:

 

下面的花括号仍然那么写没问题。在类中的const变量与non-const变量在构造函数中的位置就可以看出,构造函数体中的三个变量不是初始化行为,因为C++规定:类对象的成员变量的初始化动作发生在进入构造函数本体之前。这三句话:

 

均不是初始化,而是赋值行为。他们这三个变量的初始化发生于这些成员的default构造函数被自动调用之时(比进入构造函数本体要早)。但这对m_scores这样的基本内置类型不为真,不保证他的初始化行为一定发生在你所看到的那个赋值动作时间点之前。

对于类中的变量也是自定义类型对象时,构造函数中的赋值行为会造成一定程度上的时间浪费的,某些时候可能只能使用参数初始化列表。所有以后不管类中有没有const变量均使用参数初始化列表会更高效。

 

这个例子中的构造函数不做什么,效果和本例中的第一个版本是一样的,但是会更高效的。

第一个版本是先调用default构造函数为m_Name,m_No, m_scores,c_Sex,设初值,然后再立刻对他们赋予新值。default构造函数一切作为因此浪费了。成员函数初始化列表(本例的第二个版本)避免了这个问题,因为初值列中针对各个成员变量而设的实参,被拿去作为各个成员变量的构造函数的实参了。而且调用的是成员变量的copy构造函数。

关于最后一点有说的不清楚的请参看<Effective C++>中的第四条款。

 

我想不管是现在学C系语言的人,还是学习Java等以及其他语言的人最初的入门语言应该都是C语言吧。C语言的难易程度是否适合作为编程入门语言我恐怕没有这个能量来论述一番。我想说的是C语言中的这几个名词(RT)自从最开始出现就一直萦绕在耳边,停留在口头上,但却又总是分不清,道不明的。最近几天搜集了一些这方面的文章与帖子来总结一下,总结是一项很好的学习方法,如果你能把你所学的东西说给别人听,并且把别人说懂了,那么你就算掌握呃。如果博客对各位看官有一丝一毫的用处,那将是我最大的荣耀与欣喜了。

一:变量的声明与定义

严格的来说变量的声明是向编译器说明一个变量,这个行为是不分配内存空间的,例如:extern int ivalue;指明ivalue是别处的一个已经定义好的变量,在这里我只是使用一下,所以要向编译器先声明。声明也可以去掉类型名:extern ivalue;

但是这仅仅是狭义的声明,我们在写程序的过程中用到的更多的是广义的声明,这是与定义有着联系的。广义的声明分为两种,如下

1引用性声明:

extern int ivalue;这个例子就算是引用性声明,就是前面所述的狭义的声明。这种声明是可以出现在程序中的多处地方的。

2定义性声明:

这也是我们在平时用的比较多的,而这种声明往往也被称作是定义了。所以定义是声明的一种,这种是分配内存空间的。

int ivalue;//因为语言的性质决定了我们在使用一个变量前要先声明,这也算是一种声明,向编译器说明一个整型变量,但是编译器会为这个变量分配内存空间的,换句话说:在声明变量的同时也为变量分配了存储空间,也就是定义了。因为定义的实质就是分配内存空间。全局变量的定义只能在一处进行,否则编译器会抛出多重定义的错误。这里不多说全局变量与局部变量之间的覆盖问题。

还有一点,带有extern关键字的也可能是定义,如extern int ivalue = 11;有了这句,后面的代码中可以出现extern int ivalue;但是不能再出现类似int ivalue; int ivalue = 11;这样子的定义语句了。否则会抛错:重定义


二:赋值与初始化

上面说了定义是要为变量分配内存空间的,如int ival;但是编译器仅仅负责分配一个4字节(在32bits下)的空间用于存储ival这个变量的值。如果变量的定义没有初始化,那这个最初分配的空间的值是随机的(空间上原来存储的值)。不经过初始化的变量可能会为程序带来不安全与不确定性。所以Scott Meyers这样子的大师也建议大家最好为你的所有的变量初始化,哪怕是内置基本类型。

初始化如果根据不同的类型来分,则可分为内置类型初始化与自定义类型初始化。

内置类型的初始化:

1直接初始化:

int ival(11);这种写法可能在初学C语言的时候不常见到,但是这确实是正确的语法,并且是直接初始化。类似于调用了构造函数,但是对于int这种内置类型是没有构造函数的,但是别忘了站在C++对象模型的角度,int也算是对象类型的。(<深度探索C++对象模型>一书中有介绍)。

2复制初始化:

int ival = 11;这种形式大概是初学者所接触的最多的了,C语言的书上也很多是这种。值得一提的是,这虽然是一个等号,但这不是赋值行为,而是初始化行为。

3:除了以上两条细致的区别之外,内置类型是否初始化还要依照于变量的定义位置与变量的声明(定义)形式。

》在函数体之外的non-static变量,也就是全局变量,如int ival。并没有手动进行初始化,编译器将其存储在常量非初始化区,编译器友好的帮我们默默的做了初始化的工作了。默认值是该类型的“零值”,那int类型的就是0了,float类型的就是0.0f了,char类型的就是''(空字符),string类型的就是""(空字符串);bool类型的为“假”记为false;
//关于零值,参看林锐博士的<高质量C++/C编程指南>。在函数体内的non-static变量没有编译器的优待。

》上面提及到的变量都是non-static限制,那是仅仅因为定义的位置不同而有着不同的“待遇”。还有因变量的声明(定义)方式不同:不管在函数体内还是在函数体外,变量前用static修饰后,如static int ivalue;虽然没有进行显式的初始化,但是编译器将其存储于静态非初始化区,而且编译器会为没初始化的static变量默默的初始化为该类型的“零值”。

》这些未经过初始化(不管是显式的还是编译器默认做的)的变量除非作为赋值运算符的左操作数,否则在程序中的其余使用均是未定义行为,这种行为在程序中不易发现,而且往往会给给程序带来极大的不便,永远不要依赖于未定义行为。

PS:未初始化的变量事实上都有一个值。编译器把该变量放到内存中的某个位置,而把这个位置的无论哪种位模式都当成是变量初始的状态,当被解释成整型值时,任何位模式都是合法的值,虽然这个值不可能是程序员想要的。因为这个值合法,所以使用它也不可能会导致程序崩溃,可能的结果是导致程序错误执行和/或错误计算。


赋值:

是在编译器为变量分配内存空间的动作发生之后的某个时刻里,变量的值被刷新行为(也就是存储空间被改写)。还有就是能够在赋值运算符左侧的一定是左值(左值可以等价于内存空间来理解)。

int ival;

ival = 11; //符合赋值操作


int ival1;

ival1 = ival; //符合赋值操作


类对象的赋值与初始化:


MyClass cls(10); //调用构造函数初始化对象

MyClass cls1(cls); //调用复制构造函数初始化对象

MyClass cls2 = cls; //这里仍然是初始化行为,并且调用的是复制构造函数,而不是那个赋值运算符重载函数。

cls2 = cls1; //这句代码是赋值行为,同时调用的是赋值运算符重载函数。参看笔者另一篇博客:点击打开链接


还有一种很特殊的变量,那就是类对象中的成员变量,他们的初始化与赋值操作往往是很迷惑的。代码解释如下:

 

可能会有人和我一样最开始先入为主了,总觉得构造函数不就是用来初始化的吗?难道构造函数做的不是初始化的工作吗?构造函数做的当然是初始化的工作了,可构造函数对于这个类对象来说是做的初始化工作,这是毋庸置疑的。

但是我们这里的问题是我们怎么初始化类中的这些成员变量呢?反正上面的那个构造函数Student(...);对于这些成员变量来说做的不是初始化工作,而是赋值行为了,为什么呢?

我们知道const变量是必须要初始化的,试想一下类中的const变量是怎么初始化的呢?C++标准强制规定,使用参数初始化列表来进行初始化工作。假如类中还有一个const变量string c_Sex(c记为常量的意思);声明在三个变量的后面。那这个变量就不能和上面的三个变量放在一起了,必须这样子做:

 

下面的花括号仍然那么写没问题。在类中的const变量与non-const变量在构造函数中的位置就可以看出,构造函数体中的三个变量不是初始化行为,因为C++规定:类对象的成员变量的初始化动作发生在进入构造函数本体之前。这三句话:

 

均不是初始化,而是赋值行为。他们这三个变量的初始化发生于这些成员的default构造函数被自动调用之时(比进入构造函数本体要早)。但这对m_scores这样的基本内置类型不为真,不保证他的初始化行为一定发生在你所看到的那个赋值动作时间点之前。

对于类中的变量也是自定义类型对象时,构造函数中的赋值行为会造成一定程度上的时间浪费的,某些时候可能只能使用参数初始化列表。所有以后不管类中有没有const变量均使用参数初始化列表会更高效。

 

这个例子中的构造函数不做什么,效果和本例中的第一个版本是一样的,但是会更高效的。

第一个版本是先调用default构造函数为m_Name,m_No, m_scores,c_Sex,设初值,然后再立刻对他们赋予新值。default构造函数一切作为因此浪费了。成员函数初始化列表(本例的第二个版本)避免了这个问题,因为初值列中针对各个成员变量而设的实参,被拿去作为各个成员变量的构造函数的实参了。而且调用的是成员变量的copy构造函数。

关于最后一点有说的不清楚的请参看<Effective C++>中的第四条款。

抱歉!评论已关闭.