声明
在上下文中指定一个变量的名字,声明仅仅是让编译器知道,而没有实际分配空间。
定义
定义是为变量分配存储空间,并可能进行初始化。定义是一种声明,因为定义的同时必然会指定变量的类型和名称,然而声明却不是定义。C++中变量的定义必须有且仅有一次,而变量的声明可以多次。一般情况变量的定义在cpp文件,声明在h文件;
对于一般函数来说,函数声明在头文件中,函数定义则在源文件中,否则可能发生重复方法重复定义头文件规则;
对于类来说,一般定义在头文件中,因为编译器需要在每个源文件都看到类的定义才能编译成功;
对于inline和constexpr function,编译器需要在每个源文件都看到定义,因此通常也定义在头文件中
extern int i; // declaration
int j; // definition
extern double pi = 3.1416; // definition
初始化和赋值
类对象的构造顺序是这样的:
1.分配内存,调用构造函数时,隐式/显示的初始化各数据成员,构造函数列表的初始化方式不是按照列表的的顺序,而是按照变量声明的顺序同时初始化显隐数据成员;
2.进入构造函数后在构造函数中执行相关逻辑,初始化列表在构造函数执行前执行。
初始化是指变量在创建的同时获得的初始值,赋值是变量定义后的操作,效果是改变变量的值。
- 默认初始化规则
定义基本数据类型变量(单个值、数组)的同时可以指定初始值,如果未指定C++会去执行默认初始化(default-initialization),默认初始化行为取决于它所属对象的存储类型:
- 栈中的变量(函数体中的自动变量)和堆中的变量(动态内存)会保有不确定的值;
- 全局变量和静态变量(包括局部静态变量)会初始化为零。
2.成员变量的初始化
成员变量分为成员对象和内置类型成员,其中成员对象由它自己的默认构造函数初始化。先来看看内置类型的成员的”默认初始化”行为:
内置类型的成员变量的”默认初始化”行为取决于所在对象的存储类型,而存储类型对应的默认初始化规则是不变的。
class A{
public:
int v;
};
A g_var;
int main(){
A l_var;
static A l_static;
cout<<g_var.v<<' '<<l_var.v<<' '<<l_static.v<<endl;
return 0;
}
输出
0 2407223 0
参考Effective C++ 4:确保变量的初始化
哪些变量会自动初始化?
3.封闭类嵌套成员的初始化
再来探讨一下当对象聚合发生时成员变量的”默认初始化”行为,同样还是只关注于基本数据类型的成员。
class A{
public:
int v;
};
class B{
public:
int v;
A a;
};
B g_var;
int main(){
B l_var;
cout<<g_var.v<<' '<<g_var.a.v<<endl;
cout<<l_var.v<<' '<<l_var.a.v<<endl;
return 0;
}
输出
0 0
43224321 -1610612736
规则还是是一样的,封闭类(Enclosing)中成员对象的内置类型成员变量的”默认初始化”行为取决于当前封闭类对象的存储类型,而存储类型对应的默认初始化规则仍然是不变的。
附变量类型
-
成员变量:
写在类声明的大括号中的变量, 我们称之为 成员变量(属性, 实例变量)
成员变量只能通过对象来访问 -
全局变量:
写在函数和大括号外部的变量, 我们称之为全局变量
作用域: 从定义的那一行开始, 一直到文件末尾 -
局部变量:
写在函数或者代码块中的变量, 我们称之为局部变量
作用域: 从定义的那一行开始, 一直到遇到大括号或者return
列表初始化
const对象或引用类型以及没有默认构造函数的类只能初始化但是不能赋值。构造函数的函数体内只能做赋值,因此初始化const对象或引用类型以及以及没有默认构造函数的类的唯一机会是构造函数函数体之前的初始化列表中。
class Point
{
// const成员只能被初始化,不能被赋值
public:
Point():_x(0),_y(0){};
// 仅调用一次拷贝构造函数
Point( int x, int y ):_x(x),_y(y){}
private:
const int _x, _y;
};
构造函数赋值
class Point
{
public:
Point(){ _x = 0; _y = 0;};
// 会调用string类的默认构造函数一次(如果没有默认构造函数则只能列表初始化),再调用Operator=函数进行赋值一次
Point( int x, int y, string name ){ _x = 0; _y = 0; _name = name; }
private:
int _x, _y;
string _name;
};
从效率方面来说的,对于内置类型或复合类型,差异不会太大,但对于非内置数据类型,差异还是很明显的。构造函数需要初始化的数据成员,不论是否显示的出现在构造函数的成员初始化列表中,都会在该处完成初始化,并且初始化的顺序和其在类中声明时的顺序是一致的,与列表的先后顺序无关。如果不用成员初始化类表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次赋值操作符的调用,如果是类对象,这样做效率就得不到保障。