Coding之路——重新学习C++(1):C++基础知识盲点总结

  最近为了找工作参加了许多公司的笔试和面试,发现了以前的知识虽然学了很多,但是并不深入和系统。所以准备把一些书重新读一读,并且打算做一些总结,毕竟老祖宗教导我们“学而时习之,不亦说乎”。

1.把程序分成模块

  当我们做程序一般都是分成许多模块去做,因为这样可以保证模块之间的独立性,不会因为一个模块的改动影响整个程序。所以我们在分模块的时候最重要的就是在一系列有关的过程(函数)和它们用到的数据组你开织在一起,在C++中一般放入一个命名空间中或者一个类中。每个模块应该提供接口隐藏数据和功能函数的具体实现,让用户只看到模块的功能使用。简单的说就像开车一样,开车我们只要操纵方向盘,油门等,而把汽车内部具体怎么点火、启动发动机、轮胎旋转的具体过程隐藏了起来。

2.虚函数机制

  当子类和父类有类似的功能而不完全相同的时候,我们希望能用父类调用子类的函数,使程序具有更大的灵活性。C++就提供了虚函数机制为我们解决了这个问题。当在类中使用虚函数的时候,每个类都会产生一张表,这张表存着指向这些虚函数的指针,这就是虚函数表(vtbl)。运行时只要调用根据virtual函数名找到vtbl中相关的函数位置,就能调用虚函数。不过类使用虚函数时,每个类的对象都存了一个指针,指针指向的是vtbl。

3.模板是一种编译时机制,也就是说它在编译时就把<typename T>中的“T”替换成具体的类型(int,string等)。与手工编写的代码一样,不会造成运行时的开销。

4.枚举

  我们常常用枚举来表示一些常用的常量。枚举仍然还有自己独立的范围,如果枚举中值都是是非负数,该枚举的值就是[0:2k-1],2k是能包括所有枚举值的最小的2的幂。如果存在负数,则是[-2k:2k-1]。例如:enum e1{a = 3, b = 9},它的范围就是0~15。

5.初始化

  在初始化的时候,如果没有提供初始化式子,那么静态对象(包括全局的、命名空间的、局部静态的对象)将自动初始化为适当类型的0,而局部对象和在自由存储区里建立的动态对象(通过new、malloc建立的对象)则不会用默认值初始化。

6.字符串常量

    在程序中,编译器会把字符串常量当做是“适当个数的const字符类型数组”,像“xsk”就是const char[4]。语法上允许把字符串常量赋值给字符串指针,但是试图通过指针去修改字符串常量是错误的,如果我们希望修改字符串常量,我们需要把字符串复制到char数组中。

char *p = "xsk";
char a[] = "xsk";
p[0] = ‘s‘;        //错误,给常量赋值没意义
a[0] = "s";      //正确,a是4个字符的数组  

  另外,字符串常量是静态分配的,所以可以充当函数返回值。空字符串写成“”,它的类型是const char[1];不过当你把空字符(‘\0’)放入字符串中时,程序只会把空字符当做结尾。例如“xsk\0coding”只会被当做“xsk”。

7.常量

  (1)关键字const可以加在一个对象的声明上,使对象成为常量。因为const常量不能赋值,所以必须进行初始化。(const int i =100;)

  (2)const常量通常的值都是常量表达式,如果是这样,常量就可以在编译时求值,甚至可以不用为const常量分配内存。

const int c1 = 1;
const int c2 = f(3);    //编译时不知道c2的值,需要分配空间
extern const int c3;   //编译时不知道c3的值,需要分配空间

  另外,对于const常量数组是需要分配存储空间的,因为编译器无法弄清楚表达式使用的是常量数组中的那些元素,通常把常量数组放入只读存储器改善效率。

  (3)我们可以把一个变量的地址赋值给一个常量的指针,但是我们不能把常量的地址赋值给一个普通指针,这样就允许修改常量了,但是这是错误的。

8.指针和数组

  (1)指针的实现是希望能直接映射到程序运行所在机器上的机制。

  (2)在C++中,一般空指针用“0”表示,不用“NULL”。

  (3)在==应用于指针时,==比较的是地址,而不是指向的对象。

  (4)在数组和指针之间,只存在将数组名转换为数组开始元素的指针,反之不能把指针赋值给数组名。

  (5)对于void*指针,除了赋值操作和吧void*指针显式转化为其他类型指针外的操作都是不安全的,因为编译器不知道void*指针的具体类型,使用时必须显式转换。函    数指针和成员指针都不能赋值给void*指针。

9.引用

  (1)为了保证我们能顺利的使用引用,我们必须对引用进行初始化,并且不能进行更改。引用类似与一个指针,可以同过“&rr”来取得引用的对象地址,但是,引用不是一    个真正的对象不能像操作指针那样操作引用。

  (2)对于普通的引用“T&”的初始值必须是一个已经定义的变量,而对于const T&的初始式可以不是一个变量,甚至可以不是一个类型。

double &r = 0;                //错误,必须是变量
const double &r2 = 2;     //正确。
/*
    1.先把2进行double的隐式转换
    2.将结果存入一个double的临时变量中
    3.将临时变量作为初始值      
*/    

  (3)一般函数中都喜欢使用常量引用作为参数。需要区分对变量的引用和对常量的引用,在变量引用时容易引用临时变量,赋值将会是对即将消失的变量的引用,容易出错,并且常量引用作为函数参数容易被函数修改,这通常是不合理的。而对于常量的引用则不会出现这些问题。

10.运算符

 (1)对于运算符优先级,[作用域解析符号] > [成员选择符号、类型转换符和value++符号] > [sizeof 、new 、&取地址、++value等一元符号] > [成员选择符号] > [二  元运算符] > [位移符号] > [大于、小于符号] > [等于符号] > [按位逻辑符号] > [逻辑符号] > [赋值符号]。

 (2)一个以左值为运算对象的结果仍是左值。不过有时也应该注意下面的情况:

int *q = &(x++);    //错误:x++不是左值(值不存储在x中)

11.new 和 delete运算符

  (1)在new后,delete时必须知道为对象分配的空间大小,这说明new分配的对象比静态对象占用的空间稍微大一点,通常需要一个机器字保存对象的大小。

  (2)operator new 一般不对返回的存储进行初始化,当new无法找到空间分配时默认抛出bad_alloc异常。

12.函数

  (1)inline描述符试着给编译器一个提示,把所有的inline func函数实时处理,得出结果。而递归函数则不可能实时处理。

  (2)字符常量、常量和需要转换的参数都可以传递给const&,不能传递给非const&。如果将数组作为函数参数,传递的就是数组的首元素指针,类型T[]在作为参数传递时    转化为T*。

  (3)每当一个函数被调用时,就会建立所有参数和局部变量的新的副本,函数返回后,这些内存另做他用。所以绝对不能返回临时变量的指针和引用。

  (4)函数指针调用函数和初始化时,必须要求参数类型和返回值类型完全一致。

13.名字空间和

namespace Parser{   
 double prim(bool); double term(bool); using Lexer::get_token; using Lexer::curr_ok; }

  (2)以偏向方式解析潜在冲突

namespace His_lib{
    class String {/*...*/};
    class Vector {/*...*/};
}

namespace her_lib{
    class Vector {/*...*/};
    class String {/*...*/};
}

namespace My_lib{
    using namespace His_lib;       //来自His_lib的所有东西
    using namespace Her_lib;       //来自Her_lib的所有东西

    using  His_lib::String;    //以偏向His_lib的方式解决冲突
    using  Her_lib::vector;   //以偏向Her_lib的方式解决冲突 

    class List{/*...*/};
}

  (3)我们一般使处理错误的代码与“正常”代码分离。在导致错误的代码的同一抽象层次上处理错误很危险。完成错误处理的代码有可能又产生起错误处理的错误。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。