c++语法、语义学习心得(一)-thinking in c++

  c
语言是面向过程的编程语言,而c++是面向对象的编程语言,后者完全兼容前者。对于初学者来说,对于习惯使用c编写程序的人来说,似乎察觉不到与c++的区别,写得好的c程序也可能与c++媲美,但是无论是在习惯上还是在思维上都自觉不自觉地停留在初级c语言的水平,如何从根本上,内心深处意识到自己是在用c++,自己的思想是c++的,那么thinking in c++就是这样一本最好的圣经。
  
其实学习任何语言或者新的技术都有着一套方法学在里面,按照高中生复习备考的规律一样,制定计划,掌握方法,日积月累。所以它首先告诉我们什么是oop,如何将过程编程转换到oop模型中,而我们在这种转变过程当中应当具有什么态度,采用什么样的方法学来指导我们,促进我们,逐步改进思想和方法。而我将针对cc++的不同以及c++所特有的东西谈谈我的心得体会!
1cstructc++class
   
我们在c中使用最多的就是struct,在struct中有数据成员,当然也可以有其他结构体的存在,也可以用作链表。在c++中,我们可以将struct改造,使之具有函数成员,用来初始化数据成员,那么这就是class的雏形,引导着我们走向oop
   
然而在cstruct一般作为全局变量,到c++中也是如此,但是如何访问编译单元(某一个cpp文件)中的私有变量呢?这将无法控制,所以structc++中的使用受到限制
   
另外在c++struct中的数据成员默认都是公共变量,而具有封装特性的class中的数据成员默认都是私有变量,因此我们在使用的时候需要注明、声明这点。
2c++的构造函数与析构函数的使用顺序
既然我们已经知道class是对象的模板,属于同一个类的不同对象具有相同的属性,但是可以有不同于其他个体的属性值。那么我们在声明一个对象时,让其具有不同的属性值:初始化些数据成员;而当我们结束使用对象的时候注销该对象:清除其中一些数据成员所占据的内存。
   
通常我们会分别使用构造函数和析构函数来完成上面功能。那么编译器是如何识别构造函数和析构函数的呢?我们不得不设计构造函数和析构函数的名称与类名相同。但是假如我们希望使用不同的方法来同时达到初始化成员变量呢?我们也就不得不设计构造函数的重载(后面谈到)。
   
由于对象与对象之间可能存在一定依赖性,加入对象A是对象B的一个组成部分,那么我们在初始化B的时候,必须首先初始化A,我们在清除完B所占据内存之后才能清除A所占据内存,因此这也就告诉我们析构函数与构造函数的执行顺序是相反的。尤其是存在对象的组合的时候,特征尤其明显。
3)构造函数的重载与缺省参数的使用
    
为了类成员变量的初始化的多样性,我们考虑构造函数重载,但是编译器是如何区分构造函数的呢?大家知道构造函数没有返回值,我们只能从函数的参数多少以及参数的类型来区分了(推广出去就是不同通过返回值来区分重载函数),这实质上是编译程序在编译时编译成类似于_f_int_int的内部连接标识,(与c不同,以后将会提到编译问题)。
    
但是往往其中一个构造函数只是另外一个构造函数的一个特例,因此c++提供缺省参数来实现不同构造函数的统一调用,即在某构造函数参数表中加入缺省参数,即使用户没有输入该参数,也会自动加载一个默认的参数值,掩饰了构造函数的差异。由于编译器在加载函数时,从右往左压入参数表,因此缺省参数一般放在参数列表右边(即括号右边,而不能存在间隔甚至置于左边),(推广开去,所有的函数在使用缺省参数时均要注意这一点:位置)。
   
另外在构造函数之前往往要首先初始化常量成员变量,他们的初始化与上面不一样,见后文。
4)内联函数
   
为了加快程序执行速度,在编译时,c++编译程序往往尽可能将函数原型,参数, 返回值, 以及函数体均放入符号表中,而不是栈中,方便查找和连接速度。那么内联函数就是其中一种设计,是用来取代c语言中带参数、表达式的宏指令define的(因为c语言中使用define指令可能带来一些二义性并且不能访问私有成员)。使用内联函数实质就是在每次包含内联函数的地方都采用代码替换的方法,即宏指令替换的方法。
内联函数常使用incline进行显示说明,因此有两种方法声明和实现内联函数。
<1>
在类中,直接在声明的同时实现函数, 表示隐式实现内联函数
<2>
正常声明,而实现时加入incline说明,表示显示实现内联函数
内联函数的目的就是为了提高编译速度,所以不能在内联函数体内使用循环、switch结构等复杂的语句,同时也忌讳使用规模较大的函数体(防止代码的膨胀和臃肿)。

5)常量限定符号const及其使用
   
同样,为了改进c语言中define来定义常量的方法,使用const限定符号,禁止所修饰的变量在程序执行期间被修改。因此关键字const能将对象、函数参数、返回值及成员函数定义为常量,并能消除预处理器的值替代而不对预处理器有任何影响。
   c++
const默认为一个内部连接,在外部编译单元是不可见的,有点类似与static变量(后面提到);
   
另外c++编译器一般也并不为const分配存储空间,而是将其放入符号表中,以加快编译速度,当const被使用时,往往进行常数折叠。
   
在使用const时尤其要注意常数指针与指向常数的指针的区别。顾名思义常数指针意味着该指针不是一个普通指针,而总指向同一个地址,不能更换老家,但是家里面的摆设变化了,它是管不着的;指向常数的指针意味着该指针还是一个普通指针,只不过恰好、偶然指向一个不能被改变的常数而已,我们当然可以改变该指针所指向的地址。那么怎么样声明呢?
   
事实上const int* a; int const* a都是一个意思,而int* const a是另外一个意思,大家子个揣摩。 但是这又与声明指针的方法和习惯有点出入,因此我要说明两点。
<1>
一般而言我们声明指针时,推荐用int *a,而不常用int* a;因为我们在使用int* a, b时会给人一种二义性:a, b都是指针,还是只有a才是指针呢?如果我们使用int *a, b时那么就不会带来任何理解上的麻烦,因此特别注意这点。
<2>
更为无奈的是在我们声明返回函数是常数时,又不得不将const置于函数原型尾部,因为如果放在前面,就表示返回值为常数了。
   
所以c++还是有其不规范性的,但是我们必须理解它。
   
class中,const如何使用呢?
   const
并不属于类的,而是属于每一个对象的,意味着在这个对象寿命期内,这是一个常量,因此属于同一类的不同的对象可以具有不同的属性值,但是又不能在声明的时候赋值(与不在类中完全相反)。因此我们必须在构造函数开始之前就初始化。
   
那么在class中的const成员变量如何初始化呢?往往我们使用称之为构造函数初始化表达式表的方式来实现初始化。
   class
中的const成员函数又是干什么的呢?一般而言不修改成员变量的成员函数都尽量声明为const成员函数,同时也可以供const对象使用。同时我要说明的是:
<1>
不能修改成员变量
<2>
构造函数不可避免地要修改成员变量,因此不能成为常数成员函数。
<3>const
对象是一个稳定的对象,一般不修改其内部成员。
  
谈到这里,我们可能要问,
<1>
如何使得一个变量可以在声明时就赋值,而且作为常数,方便我们使用数组? 那其实答案就是enum变量
<2>
如何保证一个变量可以被常数对象改变呢? 答案就是用mutable代替const
<3>
如何使得一个变量属于类的,属于各对象共享的呢?答案也很简单,就是static变量

6)真正的常量可以使用枚举enum来实现。
enum
变量同样不占用存储空间,也放在符号表中,在编译时会被全部求值
使用方法见: 
<1>enum {size = 100}; int array[size]

<2>enum {one=1; two=2;three;four}; int array[three]
 推理求职three=3
但是使用enum,编译器有安全性检查,因此enumint之间不能互相转换。

7mutable修饰符号 
 classname {mutable a = 100;} classname const object; object.a = 200;

8volatile修饰符号,与const使用完全相同
但是其意义是:在编译器认识的范围外,这个数据可以被改变

9static变量
<1>
存放区间与可见性
静态变量存放于静态存储区, 而临时变量存放于非动态存储区栈 、堆中。
全局变量static :  本地文件全局可见性;extern :  本地文件、外部文件全局均可见
临时变量auto   : 默认为该类型,因此基本无用;register : 加快变量访问速度, 提高效率, 但是寄存器数量及其有限;
<2>
函数内static变量:只需要初始化一次,下次可以随时改变,当然对于内部类型变量可以不初始化,编译器会自动赋初值,通常为'\0'
<3>
类内static成员变量:属于类的,可以供各对象共享,因此也只需要初始化一次。需要说明的是:   
this
指针不属于类的,而是属于对象的,用来指向对象自身的隐式常数指针,class内部各非静态成员函数之间相互调用时默认隐式地使用了this常数指针,那么也就意味着静态成员函数不能使用this指针,因此不能访问非静态成员函数或非静态成员变量。