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

摘自:http://blog.csdn.NET/zlhy_/article/details/8442573

这几个名词(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;  //符合赋值操作


类对象的赋值与初始化:

[cpp]  view plain  copy
  1. class MyClass  
  2. {  
  3. private:  
  4.     int ivalue;  
  5. public:  
  6.     MyClass(int ival)  
  7.     {  
  8.         ivalue = ival;  
  9.     }  
  10.   
  11.     MyClass(MyClass &CS)  
  12.     {  
  13.         this->ivalue = CS.ivalue;  
  14.     }  
  15.   
  16.     MyClass& operator=(MyClass &CS)  
  17.     {  
  18.         this->ivalue = CS.ivalue;  
  19.         return *this;  
  20.     }  
  21.     ~MyClass(){}  
  22. };  

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

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

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

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


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

[cpp]  view plain  copy
  1. <span style="font-size:18px;">class Student  
  2. {  
  3. private:  
  4.     string m_Name;  
  5.     string m_No;  
  6.     float m_scores;  
  7. public:  
  8.     Student(string name,string no,float sco)  
  9.     {  
  10.         m_Name = name;  
  11.         m_No = no;  
  12.         m_scores = sco;  
  13.     }  
  14.   
  15. };</span>  
可能会有人和我一样最开始先入为主了,总觉得构造函数不就是用来初始化的吗?难道构造函数做的不是初始化的工作吗?构造函数做的当然是初始化的工作了,可构造函数对于这个类对象来说是做的初始化工作,这是毋庸置疑的。

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

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

[cpp]  view plain  copy
  1. Student(string name,string no,float sco,string sex):c_Sex(sex)  

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

[cpp]  view plain  copy
  1. m_Name = name;  
  2. m_No = no;  
  3. m_scores = sco;  

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

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

[cpp]  view plain  copy
  1. class Student  
  2. {  
  3. private:  
  4.     string m_Name;  
  5.     string m_No;  
  6.     float m_scores;  
  7.     const string c_Sex;  
  8. public:  
  9.     Student(string name,string no,float sco,string sex)  
  10.         :m_Name(name),m_No(no),m_scores(sco),c_Sex(sex)  
  11.     {  
  12.   
  13.     }  
  14. };  
这个例子中的构造函数不做什么,效果和本例中的第一个版本是一样的,但是会更高效的。

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

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


http://www.thinksaas.cn/topics/0/443/443696.html

//--------关于extern的用法------------------- 
    extern的作用就是进行声明,实现调用其他文件的全局变量,或者函数。 
由于声明可以多次,一般有的变量会被其他文件文件调用,那么一般会在该文件的头文件多声明一次改变量——extern + 变量。 
那么如果另外一个文件包含了该头文件,就可以调用该变量。 
//--------关于struct的声明定义初始化------------------- 
1、struct的定义(这里注意的是,struct定义的是类型,而不是变量;但是以上结论同样适用!) 
struct people        //定义类型 

    char name[20]; 
    int age; 
};

2、struct people;    //声明类型,同样写两句一模一样的不会错!但是类型定义也只能一次。

3、定义类型时,同时利用类型声明(定义)变量。这里类肯定是定义好了,而s1如果没有其他初始化代码,这里就是即声明又定义了。 
struct student 

    char name[20]; 
    int age; 
}s1;

   4、结构体变量的初始化,这个需要在3的前提下,这种形式的初始化可以不按照定义时的顺序,可帅气的称之为“乱序初始化” 
struct student s1 = 

    .age = 10,    
    .name = "linux",    //注意初始化结构体变量是用的是逗号,而定义结构体类型时,用的是分号。 
};

5、省略掉类型的声明(定义) 
struct 

    char name[20]; 
    int age; 
}s1; 
但是无法像4中那样单独的初始化。

6、定义结构体类型时,无法初始化结构体变量,如下写法会报错: 
struct student  //错误写法 

    char name[20] = linux ; 
    int age = 10; 
}s1;

7、于似乎再次发现一种神奇的写法:这样写也是有效的 
struct student 

    char name[20]; 
    int age; 
}s1 = 

    .age = 10, 
    .name = "linux", 
};

8、去掉类型名字,这样写也是有效的 
struct 

    char name[20]; 
    int age; 
}s1 = 

    .age = 10, 
    .name = "linux", 
}; 
定义结构体类型,同时定义结构体变量,并初始化变量,就是这么帅~~

9、struct {}a和 struct a{} 的区别 
前者是定义了a(结构体)变量,后者是定义了结构体类型a。

 

//--------关于enum的声明定义初始化------------------- 
这个就直接摘抄朱老师(朱有鹏)笔记吧。 
/* 
**************************************************************** 
*     enumeration 类型定义 
**************************************************************** 
*/ 
// 定义方法1,定义类型和定义变量分离开 
enum week 

    SUN,        // SUN = 0 
    MON,        // MON = 1; 
    TUE, 
    WEN, 
    THU, 
    FRI, 
    SAT, 
};

enum week today;

// 定义方法2,定义类型的同时定义变量 
enum week 

    SUN,        // SUN = 0 
    MON,        // MON = 1; 
    TUE, 
    WEN, 
    THU, 
    FRI, 
    SAT, 
}today,yesterday;

// 定义方法3,定义类型的同时定义变量 
enum 

    SUN,        // SUN = 0 
    MON,        // MON = 1; 
    TUE, 
    WEN, 
    THU, 
    FRI, 
    SAT, 
}today,yesterday;

// 定义方法4,用typedef定义枚举类型别名,并在后面使用别名进行变量定义 
typedef enum week 

    SUN,        // SUN = 0 
    MON,        // MON = 1; 
    TUE, 
    WEN, 
    THU, 
    FRI, 
    SAT, 
}week;


// 定义方法5,用typedef定义枚举类型别名,并在后面使用别名进行变量定义 
typedef enum 

    SUN,        // SUN = 0 
    MON,        // MON = 1; 
    TUE, 
    WEN, 
    THU, 
    FRI, 
    SAT, 
}week;

结构体,枚举,使用方法总结: 
1、结构体,枚举使用有很多相似的地方; 
2、定义枚举类型时,用的是分号。而且可以同时初始化其实的变量。 
3、枚举类型不管是类型名还是,枚举内部的变量都是全局的,所以都不能重名。而结构体内部的变量是局部的。 
4、使用枚举是可以直接用其中的变量如:SUN;而结构体需要这么访问:s1.age

最后是关于static的声明: 
static来声明一个变量的作用有二个: 
(1)对于局部变量用static声明,则是为该变量拥有全局变量的特性——在整个程序的执行期内都始终存在。因为他和全局变量一样在数据段。而不再栈上。 
同时拥有局部变量的特性——无法被外部程序访问,这个就是生命周期的问题。 
(2)全局变量或者是函数用static来声明,则起到“隐蔽”的作用——该变量的作用域只限于本文件。其他文件无法访问。 
加上extern也不行。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值