C++中对变量的操作有很多,比如赋值、初始化等。很多人都会把赋值和初始化混为一谈,下面我们就来研究一下赋值和初始化的区别。先上结论:
初始化 | 赋值 |
---|
在变量生命周期中只能进行一次 | 可以无限次数进行 | 任何类型的变量都必须初始化(基本类型无初值也可以视为调用默认构造函数,而对象必须调用构造函数进行初始化,无构造函数会调用默认构造函数,有构造函数必须调用其中一个,调用无参构造函数时可以省略括号,这也算初始化) | 可有可无 | 必须在声明时紧跟在变量名后 | 可以在变量生命周期中的任何位置 | 在类的构造函数里初始化必须写在初始化列表中 | 可以在类的构造函数里的任何位置 | 可以使用() ,也可以使用=(如果是自定义类型,必须满足构造函数只有一个参数且类的构造函数没有显式指定不能使用=) | 狭义的赋值只能使用=(重载运算符) | 引用类型初始化是指定指向的变量 | 引用类型的赋值视为对原变量的赋值 | 初始化中的=或()不应视为运算符,只能视为一种标志,因此它没有返回值(不是返回值为void类型,而是直接没有,就像一个break语句没有返回值那样) | 基本类型返回自身的引用,自定义类型可以根据需要修改,一般也返回自身引用 |
下面,我们通过几个具体例子来说明赋值和初始化的区别。 例1
int main()
{
int a = 5;
a = 3;
return 0;
}
这个例子虽然简短,但却显示出初始化与赋值在语法上的区别。当然,这并不足以说明初始化和赋值实际的区别。 例2
#include<iostream>
void myfunc()
{
static int a = 0;
static int b;
b = 0;
a++;
b++;
std::cout << a << ' ' << b << std::endl;
}
int main()
{
for (int i = 0; i < 10; i++)
myfunc();
return 0;
}
运行结果: 这里的a和b都是静态变量,为什么结果会不同?原因就在于a=1是初始化,在a的生命周期(从第一次调用到程序结束)里只会执行一次,而b=1是赋值,不属于变量声明代码,所以每次调用都会被执行。 例3
#include<iostream>
class MyClass
{
public:
MyClass()
{
std::cout << "无参构造函数被调用" << std::endl;
}
MyClass(int x)
{
std::cout << "有参构造函数被调用" << x << std::endl;
}
void operator=(int x)
{
std::cout << "operator=被调用" << x << std::endl;
}
};
void func(int t)
{
std::cout << "第" << t << "次调用func" << std::endl;
static MyClass a = 10;
static MyClass b;
b = 20;
MyClass c(10);
MyClass d;
d = 10;
}
int main()
{
for (int i = 0; i < 5; i++)
{
func(i);
std::cout << std::endl;
}
return 0;
}
运行结果: 通过本例可以看出,静态变量的初始化只执行一次,普通变量的初始化在每个生命周期都有一次,而所有变量赋值操作每次调用函数都会执行。 例4
#include<iostream>
class MyClass
{
public:
MyClass()
{
std::cout << "无参构造函数被调用" << std::endl;
}
MyClass(int x)
{
std::cout << "有参构造函数被调用" << x << std::endl;
}
void operator=(int x)
{
std::cout << "operator=被调用" << x << std::endl;
}
};
class Test
{
private:
MyClass obj;
public:
Test()
{
std::cout << "在构造函数中不初始化:" << std::endl;
std::cout << std::endl;
}
Test(int x)
{
std::cout << "在构造函数中赋值:" << std::endl;
obj = x;
std::cout << std::endl;
}
Test(int x, int y) :obj(x)
{
std::cout << "在构造函数中初始化:" << std::endl;
std::cout << std::endl;
}
};
int main()
{
Test a;
Test b(7);
Test c(5, 0);
return 0;
}
运行结果: 通过这个例子,我们能得出两个结论: 1.构造函数初始化列表中的成员会被初始化,不在初始化列表中的成员会被调用无参构造函数,如果某成员没有无参构造函数且不在初始化列表,会报错; 2.成员的初始化在构造函数中任何代码之前被执行。 通过结论1,我们可以得出,在构造函数中如果想设置成员变量,初始化比赋值速度快,因为赋值之前会先调用一遍构造函数,如果不在初始化列表中进行初始化,就会造成重复调用,引起时间浪费。 例5
#include<iostream>
class MyClass
{
public:
MyClass()
{
std::cout << "无参构造函数被调用" << std::endl;
}
MyClass(int x)
{
std::cout << "有参构造函数被调用" << x << std::endl;
}
void operator=(int x)
{
std::cout << "operator=被调用" << x << std::endl;
}
};
class Father
{
protected:
MyClass obj;
public:
Father()
{
std::cout << "父类无参构造函数" << std::endl;
std::cout << std::endl;
}
Father(int x) :obj(x)
{
std::cout << "父类有参构造函数" << std::endl;
std::cout << std::endl;
}
};
class Son :public Father
{
private:
MyClass son_obj;
public:
Son()
{
std::cout << "子类无参构造函数" << std::endl;
std::cout << std::endl;
}
Son(int x) :son_obj(x)
{
std::cout << "子类单参构造函数" << std::endl;
std::cout << std::endl;
}
Son(int x, int y) :Father(x),son_obj(y)
{
std::cout << "子类双参构造函数1" << std::endl;
std::cout << std::endl;
}
Son(int x, int y, int i) :Father(x)
{
son_obj = y;
std::cout << "子类双参构造函数2" << std::endl;
std::cout << std::endl;
}
};
int main()
{
Father a;
std::cout << "-------------------------" << std::endl;
Father b(1);
std::cout << "-------------------------" << std::endl;
Son c;
std::cout << "-------------------------" << std::endl;
Son d(2);
std::cout << "-------------------------" << std::endl;
Son e(3, 4);
std::cout << "-------------------------" << std::endl;
Son f(5, 6, 0);
return 0;
}
运行结果: 本例显示出有继承的初始化顺序:无论如何,先初始化父类,然后初始化子类初始化列表里的成员,最后执行子类构造函数中的代码。另外提一点:初始化列表里的顺序和实际初始化的顺序没有关系!!!实际初始化的顺序是按照声明顺序来初始化的,不是按照初始化列表的顺序来初始化的!!! 讲到这里,相信大家能够分辨并合理使用赋值和初始化了。今天先讲到这里,我们下期再见!
|