§2 变量的初始化、const和reference
“叮……叮……叮……”
一阵电话铃声把我从梦里吵醒,是哪个可恶的家伙啊,连觉都不让别人睡好,难得双休日不用加班,真是#%%¥# ……(此处省略213字的抱怨)
“喂!谁啊?”还没睡醒,脑袋有点晕,于是语气也有点不客气。
“师傅早!”Young的语气到是十分客气的样子。
“原来是你啊!这么早打电话找我干什么啊?”
“很早吗?”
我看了一下闹钟,“才七点五十啊!”
“啊?你果然没睡醒哦。看看清楚嘛……”Young故意拖长了最后一个字,似乎在给我改正的机会。
我揉了揉眼睛,再看了一下边上那个小闹钟,啊,原来我把长针和短针看反了,已经十点四十了。
“喔,抱歉!被你说中了,我的确没睡醒,昨天晚上,哦,不对,是今天早上3点才睡的。不过今天是周六啊,睡晚点也没关系吧?”没睡醒,脑子有点糊,一下子找不出什么体面一点的借口,还是实话实说为好。
“呵呵,没关系啦,干这行的似乎都是这样的,我知道的啦!”
“恩,那你今天找我有什么事啊?”还是转个话题为好。
“上次你给我讲的东西我都懂了,所以,我想问你今天有没有空啊?可不可以继续我们的课程啊?”Young的语气很诚恳。
“今天?让我看看。喔…… 今天我没什么安排,那要么等一下到公司碰面?”
“好啊!那就这样说定咯!”
“好啦,不会放你鸽子的啦,放心啦!”
“恩,那我就先去公司等你,你快点来哦。”
“好的,就这样,拜拜!”
挂了电话,赶忙起床。
……(三十分钟后到了公司)
一进我们小组的工作室,就看见Young已经坐在我的位子上,捧着本书,似乎是《The C++ Programming Language》。
“你这么快就来啦?没吃早饭吧?”我刚想叫她,结果她已经看到我了,又把我的台词抢了。
“恩,还没吃。一般我周末都只吃两顿的。”
“那好,等一下一起吃午饭吧,还是我请哦!”
“你这个徒弟到是够乖的,总是请师傅吃饭啊。恩,这顿饭就算补我的睡眠了,就不责怪你那么早打电话把我吵醒了。”
“呵呵,实在抱歉啦,我也不知道这么晚了,您老人家还在梦中神游。”
“老人家?咳咳…… 注意用词!我看上去有那么老吗?”
“没有啦,我用这个词的目的只是为了表示对您的尊敬”Young一脸鬼笑,我也真是那她没办法。
“好吧,那就开始今天的课程吧。恩,让我想想今天该教你点什么……”说这句话的时候,脑中闪过一个个C++中的特性和关键字,“我们就从C++里面的基本类型讲起吧。”
“好啊。喔,我知道C++支持原来C里面的所有的基本类型,就像int、char、float、double等等。这些东西的用法和原来C里面一样吧。”Young在说这些的时候,眼神中带着一点自信。
“恩,基本正确。”
“啊?难道还有不同?”自信的眼神没了,Young又进入了疑问的状态。
“的确还有不同。就这些C++内建的基本类型而言,C++支持C中的使用方法,但是还增加了一点特性。我问你,怎么对一个int类型初始化?”
Young很有自信的在屏幕上打出:
int a = 3; //定义int类型变量a,并且初始化为3
“正确,无论在C和C++中,这样做总是正确的。但是,你知道另外一种初始化的方法吗?”
“……不知道。”
“其实还可以这样初始化。”我在屏幕上打出:
int a(3); //定义int类型变量a,并且用构造函数语法初始化为3
“哦,原来还可以这样啊?那这个和原来的方式有什么不同呢?”
“其实这种方式对于一般的内建数据类型而言,基本上没有区别的。但是这种方式对于C++而言,有着很重要的价值,否则,它也不会存在啊。例如下面这个例子。”
int* p = new int; //定义一个指向int类型的指针,并且为它动态分配一块内存
*p = 3; //将p指向的对象赋值为3
“这样写的确没有什么问题,但是我们可以利用构造函数语法将这两行代码变成一行。就象下面这样。”
int* p = new int(3);
“这样就既完成了动态分配,也赋了初值。还记得C里面用来动态申请内存的函数吗?”
“记得,就是那个malloc()!”
“是的。这个函数和C++里的new这个运算符的本质区别就在于new会自动地去调用对象的构造函数,而malloc不会。这也是new的优点,它能保证动态生成的对象在生成的时候就被初始化,这个特点可以避免很多可能发生的错误。”
“我没听错吧,你说new是一个运算符?”
“没错,new是一个operator,其实C++里的运算符就象函数一样,其实就是一种特殊的函数。恩,等到以后给你讲运算符重载的时候我再给你详细解释。”
“哦,大致听懂了。你刚才说的在建立一个对象的时候没有初始化所带来的麻烦,我以前在学C的时候碰到过。恩,我会记住这点的。”
“好,接下去我们讲const这个关键字的使用。”
“const?这个关键字我在看书的时候看到过,而且被它搞晕了,一想到这个就觉得不爽。”Young似乎有点沮丧。
“是不是关键问题出在和指针的关系上啊?”
“是啊!原来师傅你也深有体会啊!”
“你这张嘴真是不饶人啊,厉害!算了,我不和你计较。还是先说区分方法。其实要搞清楚它和指针的关系很简单,只要看const这个关键字出现在*之前还是*之后。如果出现在*之前,就表示这个指针所指向的对象是const的;而如果出现在*之后,那么这个指针本身就是const的。当然,我想对于前后都有这种情况就不用我说了吧?”
“喔!原来是这样,这回知道了!”
“还有,由于const的对象是不能被赋值的,所以const对象一定要初始化。”
“恩,记住了。”
“顺便教你一种写法,可以直接从字面上读出const和指针的关系。就像这样”
char *const cp; //const pointer to char
char const* pc; //pointer to const char
“运用前面教你的方法,很容易区别出这两个指针的不同。你试着用从右到左的顺序将它念出来。”
“从右到左?倒着念?好的,我试试。第一个是:‘cp const * char’,第二个是:‘pc * const char’”
“恩,接下去,试着在变量名之后加上‘is a ’,再将‘*’改成‘pointer to ’,再把它念一遍。”
“好的。第一个是:‘cp is a const pointer to char’,第二个是:‘pc is a pointer to const char’。啊!这样不就区分出两者的区别了啊!好神奇啊!”
“对了,用这种方式来书写,能够直接读出两者的区别。对于第二个来说,也可以将const这个关键字放在最前面,对于编译器而言,‘const char* pc;’和‘char const* pc;’是一样的,可是后者对于程序员而言更容易读懂。”
“这回终于搞清楚了,以后写程序的时候我一定会注意的,写得清楚能够给别人方便,也给自己方便。”
“不错!能够领悟到这点很不错!要记住,‘从软件工程的角度而言,程序的清晰性是最重要的!’”
“恩,记住了!”
“最后我们再来看看引用(reference)和指针(pointer)的区别和联系。引用又可以称作为变量别名它在它作用域的任何地方都代表其最初被初始化的那个对象。例如,”
int a = 3;
int& r = a;
“在这以后的代码中,r就是变量a。两者完全一样。对于r的任何操作,其实都是作用在a的上面的。而指针我想你已经在学C的时候学过了,应该熟悉了它的用法和特点。为了让函数能够改变调用时送入的变量的值,我们就要使用指针或者引用。例如,”
void swap(int& a, int& b) //利用引用,交换a和b
{
int t;
t = a;
a = b;
b = t;
}
void swap(int* a, int* b) //利用指针,交换a和b
{
int t;
t = *a;
*a = *b;
*b = t;
}
“看到指针和引用的区别了吗?指针在使用其指向的变量的时候,需要使用*这个解引用(dereference)运算符;而引用就是其引用的对象的别名,直接使用就可以了。但是要注意,利用引用的特点,在函数里改变参数对象的值的做法,缺少可读性,比较容易出错,所以在写这样的代码的时候一定要小心。使用引用或者指针来传递参数其实还有一个优点,就是能够提高程序的效率,避免拷贝构造函数和析构函数的调用。所以,往往比较常见的是‘const T& ref’这种形式作为形参,T代表C++某种内建类型或者用户自定义类型。最后还有一点要注意的就是,引用在初始化之后就不能改换引用的对象,也就是不能再次引用其他对象,所以,引用在定义的时候就必须被初始化。”
“恩,懂了。今天你教了我好多东西哦!喔…… 时间不早了,该去吃饭了,难道你不饿吗?”
我一看屏幕上的时间,天哪!都已经下午一点多了。此时,我的肚子也很配合的叫了一下。
“看你饿的!那就快点走吧!”Young似乎也饿了,一直在催我。
而我,一直在想,是不是带了这个徒弟以后,我的胃总有一天会出问题啊……