1.“你使用一个语言特征是因为你需要它,而不是因为它存在”,学习一门程序设计语言,并不需要掌握其全部的语法,关键是要学习使用语言来解决实际问题的方法。如果你掌握的语法和程序设计方法能够高效地解决实际工作中的问题,那么表明你已经掌握了这门语言。
2.C++标准定义了标准库的接口,但未实现,这是语言实现的任务。编程语言的实现就是编译器,连接器或者解释器。语言实现有多重不同的版本,在符合标准的情况下,可以适当扩展,但一般都实现了标准化库,而且接口保持一致。
3.代码段包含源程序可执行语句序列,静态数据段存放全局变量,静态变量和符号表等,堆栈段留给函数和线程使用,堆和自由存储空间不属于程序,而属于操作系统。
4.MS C++/C中应用程序的启动函数为mainCRTStartup()或者WinMainCRTStartup(),同时在该函数的末尾调用了main或者WinMain,然后他们的返回值调用库函数exit().
5.命令行参数是由启动程序截获并打包成字符串数组后传递给main()的一个形参argv的,而包括命令字(可执行文件名称)在内的所有参数的个数传递给形参argc。
6.C++不同的编译器采用不同的Name-Mangling来进行名字修饰,这将导致不同语言实现之间的连接器不兼容。在使用不同编程语言联合开发软件或者开发程序库的时候,需要统一全局函数、全局变量、全局常量、数据类型等的链接规范。
7.全局变量在进入main前创建,在main结束后销毁,不同编译单元中的全局变量不应该具有依赖性,因为无法保证初始化顺序。
8.预编译伪指令,类(型)定义,外部对象声明,函数原型,标识符,各种修饰符号(const,static等)以及类成员的访问说明符和连接规范,调用规范等仅在编译器进行语法检查,语义检查,生成目标文件及连接的时候起作用;容器越界访问,虚函数动态决议,函数动态连接,动态内存分配,异常处理和RTTI等则是在运行时才会出现和发挥作用。
9.派生类改变了虚函数的访问权限在用基类指针访问时不起作用,用基类指针访问虚函数取决于基类的访问权限。
10.对于字节序的问题:如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。但是,如果你的程序要跟别人的程序产生交互呢?比如JAVA采用大端方式存储数据,C++采用小端存储,如果C++传递一个指针给JAVA,那JAVA在翻译的时候就会错误。
11.由于地址线的最后两位用于字节选择, 用于逻辑控制, 而不和存储器相连, 所以需要自然对齐,比如int类型地址需要时4的倍数,这样一次就能取完,比如一个int的地址是0x03的话,先是按地址0x00取int的第一个byte,然后按地址0x04取剩下的三个byte,所以原本一次访存搞定的事情,却做了两次。short类型需要是2的倍数,这样也能一次取完,char类型随意。
12.对于C++,遍历数组最好使用先行后列,因为对于大型数组涉及到页面文件交换次数以及cache的命中率,而不是循环次数。
13.字面常量是只读的,无法取一个字面常量的地址,无法通过字面常量的地址修改其中的字符。比如char *p=”abcd”;*(p+2)=’k’;这种修改是错误的。
14.宏常量本质是字面常量,在编译时替代为所代表的字面常量。在C中,const常量是值不能修改的变量,会给它分配空间。在C++中,对于基本数据类型,编译器把它放到符号表,不分配空间,对于大对象需要分配存储空间。如果强制声明为extern的const常量或者取const常量的地址等操作会使得编译器分配空间。
15.const常量只是编译时强类型安全检查机制的一种手段,无法阻止恶意的修改。
16.C语言中,const符号常量默认是外连接(分配存储)的,多个编译单元无法同时定义一个同名的const符号常量。在C++中,const符号常量是内连接的,不同变异单元同名的const符号常量是不同的符号常量。
17.const与define的比较:const常量有数据类型,可以安全类型检查,define容易产生意料不到的错误,const常量方便调试,应尽量使用const常量。
18.类中无法初始化非静态const数据成员,只能在构造函数的初始化列表中初始化。可以使用枚举常量(只能表示整型)或者静态const数据成员。
19.只有静态常量数据成员才能在类中初始化。
20.实际应用中如何定义常量 P90
21.开发类库的时候不用担心使用者仅使用一小部分功能,却要包含整个类定义而会导致代码体积增大。
22.函数调用中的参数传递本质就是用实参来初始化形参而不是替换形参。
23.线程函数不使用普通的堆栈,而使用线程自己的堆栈,同时对共享的数据要进行保护,避免使用static局部变量,因为同一个函数可以同时作为多个线程的函数。
24.函数堆栈是调用到它的时候才动态分配的,在函数退出的时候归还给程序堆栈段,这样不会浪费空间。而且函数堆栈段是在与预先分配好的内存空间上创建,不需要搜索,所以相比动态内存分配速度快而安全。
25.函数堆栈有3个用途:进入函数前保存环境变量和返回地址,进入函数时保存实参的拷贝,在函数体内保存局部变量。
26.函数调用规范:_cdecl是C++/C函数的默认调用规范,参数从右到左依次传递并压入堆栈,这种方式利于传递个数可变的参数;_stdcall是winapi函数的调用规范,参数从右到左传递并压入堆栈,当函数有可变个数的参数时转为_cdecl;_thiscall是C++非静态成员函数的的调用规范,不能使用个数可变的参数,this指针保存在ECX寄存器而不压入堆栈,其他与_stdcall相同;_fastcall将实参直接传递到CPU寄存器,不能用于成员函数。
27.凡是接口函数都必须显示地指定其调用规范,除非接口函数是类的非静态成员函数。如果不指定调用时会使用环境默认的调用规范,这是如果你的程序使用的调用规范和它们的默认调用规范不一致的话,就会出现错误(编译和链接没问题,运行崩溃)。
28.在使用不同编程语言进行软件联合开发的时候需要统一函数、变量、数据类型、常量等的链接规范,特别是不同模块之间的接口,最常用的是extern ”C”链接规范.
29.return string(s1+s2)与string temp=s1+s2;return temp;两者的效率问题:前者直接把临时创建的对象初始化在外部存储单元中,后者先创建,然后调用拷贝构造函数,然后复制给外部存储单元,然后内部析构temp。
30.extern用来表示永久生存期限的变量和函数。auto和register用来标示临时生存期限的变量,只能用于声明局部变量和局部常量。局部变量默认具有auto存储类型。全局变量和全局函数存储类型是extern。全局常量(即加const修饰符)默认是static。static全局变量和全局函数只能被同一个编译单元的函数调用。
31.连接类型分为外连接,内连接以及无连接。无连接是一个仅能够在声明它的范围内被调用的名字。
32.malloc申请内存失败不是非法情况,而是错误,此时应该用if语句来解决错误,而不实用assert,否则在Release版本中失效。
33.不要妄想通过数组名达到访问整个数组的目的,因为数组名只是个指针,无法预知大小。所以数组也不支持直接赋值。C++未对数组是否越界进行任何检查。对于动态分配的数组在程序的某个地方保存了数组的大小,静态分配的则不保存。即使动态分配也不适合进行大小检查,因为影响效率。
34.delete语句在执行时确认q是否指向一个数组以及查找数组的大小信息会极大地影响它的执行效率,所以只有在[]出现在指针前面才会去某个地方查找数组的大小信息,否则它便假设用户只删除一个对象。
35.char(*p)[4]=new char[5][4];多维数组指针的声明形式,p是指向包含4个元素的数组的指针;delete []p;删除形式
36.字符数组时元素为字符的数组,字符串是以’/0’为结束符的字符数组(因为字符数组长度可变但无法记录自己的长度)。可见字符数组不一定是字符串。
37.实际上任何成员函数的代码都是独立于类的对象而存在,只是调用的时候需要与具体的对象绑定而已。编译器最终会把所有的成员函数经过Name-Mangling的处理后转成全局函数,供所有对象想用,调用时加入this作为第一个参数。
38.当你的UDT/ADT包含数组成员时,最好使用指针或引用传递该类型的对象,并且防止数组越界,因为它会覆盖后面的结构成员。
39.对象不能自包含,但可以自引用,因为任何类型的指针大小都一样。
40.出于对齐的考虑,每个对象的存储空间中可能会存在填补字节,这些字节是上次使用留下的“脏值”,所以不能直接比较大小。
41.按照从大到小的顺序从前到后依次声明每一个数据成员,并且尽量使用较小的成员对齐方式。这样既能满足自然对齐要求,同时尽可能节省内存空间。
42.从大到小排列符合类型可以保证不同的边界对齐方式(8字节,4字节,2字节,1字节)下各个成员的偏移不会改变。从小到大排列会在中间留下空洞,这些空洞可能会随着对齐方式的调整而被压缩甚至吸收掉。
43.带参数的宏定义不是函数,没有函数的开销,但每一次调用会生成重复的代码,使得代码体积增大。所以对于较短的重复代码段可以使用带参数的宏定义。#undef取消宏定义。inline函数不能完全取代宏,宏可以定义数据和函数混合的代码段。
44.当需要注释一段代码的时候可以使用条件编译#if 0 来注释,避免双重注释的问题。
45.条件编译时由编译预处理器来处理,所以无法计算有变量或sizeof表达式,只能用常量。
46.#error用于输出与平台,环境等有关的信息,比如#ifndef(WIN32) #error ....#endif,输出错误信息后即终止,程序不会进入编译阶段。
47.#pragma用于执行语言实现所定义的动作。
#pragma pack(push,8)
#pragma pack(pop)
#pragma warning(disable:4069)
#pragma comment(lib,”kernel32.lib”)
48.构串操作符#只能修饰带参数的宏定义,它将实参的字符序列转换成字符串常量(而不是实参代表的值)。合并操作符##将其左右的字符合并成一个新的标识符(不是字符串),,使用合并操作符时其标识符必须预先定义。
49.编译器为每一个多态类(具有虚函数的类)至少创建一个虚函数表(vtable),其实是一个函数指针数组,这个表中保存了这个类所有的虚函数地址以及该类的类型信息(RTTI需要用)。每一个多态对象都有一个隐含的指针成员指向这个vtable,这就是vptr。
50.p207中关于成员函数覆盖和隐藏的例子好好理解下。
51.C++支持的运行时多态特性有两种,一种是虚函数机制,一种是RTTI。
52.静态和非静态成员函数最终都被提取出来放在代码段并为该类的所有对象共享。
53.虚函数表放在程序静态数据区,第一个位置是类信息指针,指向type_info对象,这个对象也放在程序静态数据区。
54.如果基类已经插入vptr,那么派生类将继承和重用该vptr。派生类如果从多个基类继承或者有多个继承分支,而其中若干个继承分支上出现了多态类,那么派生类将从这些分支中的每个分支继承一个vptr,同时生成多个vtable,每个vptr指向一个vtable。P215-P228
55.构成“重载”的重要标志是“函数位于同一个作用域中”。
56.只能重载为成员函数的运算符确保了调用它们的的对象(左操作数)必须是重载了它们类的对象,不能重载为成员函数的运算符一般是因为发起调用的对象并非必须是重载它们类的对象。
57.参数的默认值只能放在函数的声明中,而不能放在定义中。默认值可能会导致重载函数二义性。
58.一般来说, 凡是用作容器元素类型的class都需要重载“=”、“==”和“<”运算符。除了函数调用操作符“( )”外,其余运算符重载函数不能有默认参数。
59.内联函数在debug中根本就没有真正内联,因此能调试。关键字inline必须与函数定义体放在一起才能使函数真正内联,仅放在函数声明前面不起任何作用。建议inline关键字不要放在函数声明中,因为用户不应该知道函数是否真正需要内联。
60.static函数不能定义为const成员函数,因为static函数时全局函数形式上的封装,它不能访问类的非静态成员(无this指针)。
61.异常仅仅是通过类型而不是通过值来匹配,因此catch块的参数可以没有参数名称。每一个try块后面必须至少跟一个catch块。找到一个符合的catch块后就会跳过后面的catch块,接着执行后面的正常代码。
62.异常对象创建在专用的异常堆栈上,不要企图把局部对象的地址作为异常对象抛出。
63.可以使用set_unexpected来注册一个回调函数来取代标准库函数unexpected( )的功能,即把调用terminate( )函数改为调用用户提供的回调函数。该函数在未捕获异常是调用。
64.运行时当异常抛出时需要执行具体的异常类型匹配操作,而不是编译时就匹配好的,这就直接导致RTTI机制的诞生。
65.RTTI只能用于多态类的对象,因为只有多态类才会有vtable,才会有typeinfo的指针。VC 6.0对其支持不好。
66.RTTI要求程序维护一颗继承树,dynamic_cast<>能够判断对象与目标类型之间是否有is-a关系,这就需要在运行时遍历继承树。typeid()不需要继承树,通过vptr来获取type_info即可实现,与虚函数的动态绑定开销一样。
67.new和delete是运算符,malloc和free是标准库函数,动态对象的创建调用构造函数,销毁时需要调用析构函数,不能把这些任务强加给库函数,所以产生了new和delete。如果用malloc申请动态内存,那么需要自己手动初始化以及销毁,无法调用构造函数和析构函数。
68.plain new/delete是普通的new和delete,在失败时抛出异常std ::bad_alloc。nothrow new/delete是不抛出异常的new,失败时返回NULL,语法是char *p=new(nothrow) char[4] ; placement new/delete允许在一块已经分配成功的内存上重新构造对象或对象数组,不用担心内存是否分配失败,因为它不分配内存,它唯一做的就是调用对象的构造函数。
69.placement new的主要用途就是反复使用一块比较大的动态分配成功的内存来构造不同类型的对象或者他们的数组。用placement new构造起来的对象或其数组要显示地调用它们的析构函数来销毁,千万别使用delete,因为其大小不一定等于原来分配的大小,因此使用delete会造成内存泄露。P299
70.new对于非内部数据类型的对象而言它在创建对象的同时完成了初始化工作。用new创建对象数组只能使用默认构造函数。Obj *object=new Obj ;和Obj *object=new Obj ();等价,都是调用了默认构造函数。
71.多次delete一个不等于NULL的指针会导致运行时错误,但是多次delete一个NULL指针没有任何危险。不要将静态创建的对象来初始化auto_ptr,防止auto_ptr自动删除该静态对象引起错误。因为静态对象不能通过delete来删除。class1 c1;class1 *c=&c1;delete c;这种程序会出错。