前言
如果你做过c/c++的开发,如果你参加过c/c++的面试,那么初始化这个问题无疑是必考的题目。
开始的时候,我也很奇怪,初始化这个东西有什么好讲的,简单的说不就是没有给初值么,等看到很多错误,调试很多代码,吃了亏,上了当之后,才明白别人再怎么强调初始化的重要性都不为过。
java这点很好,必须初始化,否则编译不过。正向有个老师说的java就像是一个被保护好的孩子,你一旦有点问题就阻止你,但是c/c++就不是了,给你自由,出问题你自己负责。
一内置变量初始化
1 基本变量
这里主要讲内置变量int, float ,long ,double,bool等
首先说初始化的概念:
在定义变量的时候,给变量赋予初值,叫做初始化的变量,否则就叫未初始化的变量。
没有被初始化的变量他们的值是什么?
如果是局部变量,则是随机值,如果是全局变量则为0.
未初始化只有一个好处,如果初始化变量位于代码段中,会占用一定的空间。但是未初始化的坏处几乎是全部。
个人建议,如果你没有pclint等检查工具检查你的代码,最好给局部变量赋予初值,防止出现一些莫名其妙的错误,查问题耽误太多时间。
(ps,pclint检测的时候,如果你初始化了,但是却没有用这个变量,则会报错)
举个例子:
#include<iostream>
using namespace std;
int main()
{
int sum;
for (int i =0 ;i<10;i++)
sum += i;
cout<<sum<<endl;
}
我电脑里面运行的结果是2281105。而不是你想要的55.
你可能会说这个很简单,但是现实中编程序,开始有一堆的变量,我记不太清,c89的时候c语言必须所有的变量都在开头的时候声明,这样会导致,你声明了一堆变量,有些你要初始化,有些你不初始化,就会很麻烦
关键在于很多crash由于没有初始化导致的,比较麻烦的事情是,由于是随机值,有些环境是ok,有些环境出问题,而且还不是必现的,查问题相当困难。
另外讲一下 c++ primer里面比较蛋疼的一句话。
“初始化不是赋值”。
初始化有两种情况:
int a = 1; //赋值初始化
int a(1); // 直接初始化
赋值是什么?
a = 2;
你可以理解为定义a的时候的=号和其他时候的=不一样。这个在复杂类里面才能看出来区别,这里看不出来。
直接初始化比赋值初始化效率高一些。
2 常量
常量必须初始化,因为常量不能被修改。
const int a = 5; // ok
int b;// 错误,没初始化
a = 3;//错误,被修改
这里加一句,书上看到的,虽然和初始化没关系。就是有const限定符的函数外定义的变量,默认是只能在这个文件中访问的,别的文件中访问需要加extern.
a.cpp里面
const int a =3;
int b; // 相当于extern int b =0;
const int c =4;// 如果b文件中用到,需要声明为 extern const int c = 4;
b.cpp 里面
int b;//编译的时候会报错,重复定义,修改为extern
const int a = 3;// ok
extern const int c;//错误,该声明没有定义。
(ps 我会在后面的声明和定义里面讲一下这个内容)
3 引用
引用必须初始化,引用指向的对象不能变,但是对象的内容可以变。
int a = 3;
int b = 4;
int &c =a;
int &d;// 编译会报错,引用必须初始化,
c = b;
这个时候a,b,c的值是多少呢?都是4。
c = 5;
这个时候呢?a = 5, b= 4,c =5;
所以上面的那句c = b;相当于c = 4,也就是a =4;后面对c的所有操作都是改变a的值。c不会指向其他对象了。
4枚举
枚举是常量
enum test
{
a,
b,
c
};
这个时候a = 0, b = 1 ,c =2, 枚举成员的第一个默认初始化为0,
enum test
{
c=1,
d,
e=5,
f
};
这个时候d =2, f =6,没有初始化的默认为它前面的那个值+1;
5 数组
int a[5];//这些值都是随机值
int b[5] ={1,2,3,4,5};//全部都初始化了
int c[5]={1,2};//相当于{1,2,0,0,0}
int d[100] ={0};//相当于将数组全部初始化为0
6 字符串
字符串是c语言的特性,它是一种特殊的产物;举个例子
char *p = "abcd";
这个和
char p[]="abcd";
char p[5]={'a','b','c','d','\0'};
效果是一模一样的,char *p = "abcd";这句话很特殊,编译的时候会报警告,说不建议这么写,但是实际上这么写也没有问题。
int a =5;
int *b =&a;
char p = 'a';
char *q = &p;
你会发现如果用printf打印,是没有问题的,因为你已经指定好了类型。但是如果用cout;
cout<<b<<endl;
cout<<q<<endl;
这个时候b打印出来会是个地址,而q这个时候,cout就会把它当成一个字符串,就会打印直到遇到'\0',而这个时候输出的结果就要看a的值,你可以试试改变a的值,则每次打印的结果不一样。
7 指针
指针没有初始化,就是野指针,指向一个随机的地址,破坏性很大。指针一定要初始化。
二类的初始化
1 类成员变量初始化
类成员的初始化,不能再定义的时候初始化,要在构造函数里面初始化。
class A{
public:
A();
private:
int data;
static int st_data;
const int const_data = 10;
};
简单总结一下:
1.1普通变量,两种初始化方式:
第一,初始化列表
A::A():data(0)
{
}
第二,赋值
A:A()
{
data = 0;
}
如果不初始化,则遵守上面的规则,内置类型随机值,类的话调用构造函数。
1.2 静态变量
int A::st_data = 0;//类外初始化
它是所有类共享的,和对象没有关系
1.3 const 常量
创建的时候初始化,见上面的例子
1.4 引用变量
在初始化列表里面初始化
class B{
public:
B();
B(A a);
private:
A &m_a;
}
B::B(A a):m_a(&a)
{
}
5 const static的
short 类型的在类定义初始化
float在构造函数中初始化。
2 类对象初始化
局部对象没有初始化,它的值是多少,开始我想这个问题的时候,总和上面的内置类型的变量弄混淆,如果是自定义的类对象变量,它的值一般为空。
例如
string a;//输出之后a就是空的。
为什么呢?和上面的似乎有些矛盾,其实并不矛盾。上面的是基本类型。
类呢,本身有个默认构造函数,它会初始化对象。
受上面的例子的毒害,我一直认为,模式构造函数如果不声明,它会将类中的元素给初始化为0或者空值,但是实际情况并非如此,看下面的例子。
#include<iostream>
#include<string>
using namespace std;
class A
{
public:
int data;
friend ostream& operator<<(ostream&,const A&); //同理
};
ostream& operator<<(ostream&,const A& a)
{
cout<<a.data<<endl;
}
int main()
{
A a;
cout<<a.data<<endl;
cout<<a;
}
我这里的输出是
2281060
2281060
2281060
不过和编译有关系,有的时候也能碰巧为0,怎么验证这个值是随机值呢,你改变一下代码的顺序,或者在class A里面随便加几个不用的变量等等,就会发现输出结果发生变化了。
但是如果我们自己写构造函数,必须先在类里面声明一下。
A::A()
{
data = 0;
}
{
data = 0;
}
那么上面的结果就一定是0了。
可见string 的默认构造函数在标准库就将里面的char* 初始化为了0