C++学习笔记

1、std::end 是操作符,将它写入输出流时,具有输出换行的效果,并刷新与设备关联的缓冲区

2、使用命名空间,程序员可以避免与库中定义的名字相同而引起无意冲突(如何自己添加命名空间呢)

3、空格符不允许出现在预处理指示中

4、非const变量默认为extern,要使const变量能够在其他的文件中访问,必须指定它为extern

5、任何class只要带有virtual函数几乎确定应该也有一个virtual析构函数(工厂模式的delete)

6、为子类写copy函数时要copy父类的属性

7、string 对象和 bitset 对象之间是反向转化的:string 对象的最右边字符用来初始化 bitset对象的最低位。

8、bitset to_ulong函数返回一个unsigned long值,该值与bitset对象的位模式存储值相同。仅当bitset类型的长度小于或等于unsigned long的长度时,才可以使用to_ulong,否则产生运行时异常

9
const int a = f();
int b[a];
错误的,因为a要等到运行时才知道值,数组需要在编译时知道大小

10、如果可能的话,除非所指向的对象已经存在,否则不要先定义指针,这样可避免定义一个未初始化的指针。如果必须分开定义指针和其所指向的对象,则将指针初始化为0,,因为编译器可以检测出0值的指针,程序可以判断指针是否指向一个对象。

11、C++允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作。而计算数组超出末端位置之后或者数组首地址之前的地址都是不合法的。(编译的时候都不会报错)

12、
typedef string *pstring;//把string* 重命名为pstring
const pstring cstr;
则cstr是什么类型呢?(string * const cstr)从右往左看,对象是一个指针,类型为pstring,所以const的应该是一个指针

13、字符串字面值的类型是字符常量的数组

14、永远不要忘记字符串结束符null

15、调用者必须确保目标字符串具有足够的大小

16、使用strncpy和strncat比strcpy和strcat安全

17、
char arr[0]; //error
char *cp = new char[0]; // ok

18、
-21 % -8 = -5
-21 % 8 = -5
21 % -8 = 5

19、如何判断运算溢出???

20、浮点数于0比较怎么做??

21、&&和|| 有短路作用

22、位移操作>>和<<的右操作数不可以是负数,而且必须是严格小于左操作数位数的值

23、
使用复合赋值操作时,左操作数只计算了一次; i += 1;
使用相似的长表达式时,该操作数则计算两次; i = i+1;

24、f1() * f2(),在做乘法操作之前,必须调用f1函数和f2函数,然而,我们却无法知道先调用f1还是f2

25、如果要修改操作数的值,不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把表达式分割成两个独立的语句

26、一个表达式里,不要在两个或者更多的子表达式中对同一对象做自增或者自减操作; b = (a++)-(a--);

27、
int *pi = new int;    //pi指向未知的值
int *pi = new int();    //pi指向值为0

28、
int *p = 0;
delete p;    //ok

29、
包含signed和unsigned int型的表达式,表达式中的signed型数值会被转换成unsigned型。
unsigned int a = 10;
int b = -11;
if(a+b)    //为true

30、
dynamic_cast 支持运行时识别指针或引用所指向的对象。
const_cast 将换掉表达式的const性质
static_cast 编译器隐式执行的任何类型转换都可以由static_cast显式完成

31、形参的初始化与变量的初始化一样,如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则它只是实参的别名
void f(int &n);
f(1);    //error
void f(const int &n);
f(1);    //ok
如果不打算对一个引用的值进行修改,应对其加const

32、指针引用:voif f(int *&p);

33、传递容器,如vector,使用引用类型,或者改为传递容器的迭代器

34、通过引用传递数组:void f(int (&arr)[10]),只能传元素个数是10的数组

35、
千万不要返回局部对象的引用,会导致运行时出错
千万不要返回指向局部对象的指针

36、默认实参既可以在函数声明指定,也可以在函数定义指定。但在一个文件中,只能为一个形参指定默认实参一次。

37、在头文件中加入或者修改内联函数时,使用了该头文件的所有源文件都必须重新编译

38、如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的

39、C++允许使用函数指针指向重载函数,指针的类型必须与重载函数的一个版本精确匹配,否则会导致编译错误

40、IO对象不可复制或赋值

41、如果程序崩溃了,则不会刷新缓冲区

42、将一个容器复制给另外一个容器时,类型必须匹配,容器类型和元素类型都必须相同

43、通过传递一对迭代器,可以实现一种容器内的元素复制到外一种容器

44、
容器元素类型必须满足以下两个约束:
元素类型必须支持赋值运算
元素类型的对象必须可以复制
引用不支持一般意义的赋值运算,因此没有元素是引用类型的容器
IO库类型不支持复制或赋值,因此,不能创建存放IO类型对象的容器

45、避免存储end操作返回的迭代器; for(; it != v.end(); ++it){}

46、一旦类定义完成后,就没有任何方式可以增加成员了。

47、在类内部定义的函数默认为inline

48、将关键字const加在形参表之后,就可以将成员函数声明为常量

49、可以在类定义上指定一个成员为inline,作为其声明的一部分,也可以在类定义体外部的函数定义上指定inline

50、前向声明(一般用来编写相互依赖的类),不完全类型,只能用于定义指向该类型的指针及引用,或者用于声明使用该类型作为形参类型或者返回类型的函数

51、在创建类的对象之前,必须完整地定义该类,而不只是声明,同样,在使用引用或者指针访问类的成员之前,必须已经定义类,因为只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员,可以有指向自身类型的指针或引用

52、类定义之后可以接对象定义类表,定义必须以分号结束

53、如果函数在类定义体之外,这返回类型的名字在类作用域之外,如果返回类型使用有类定义的类型,则必须使用完全限定名

54、必须在类中先定义类型名字,才能将它们用作数据成员的类型,或者成员函数的返回类型或形参类型

55、构造函数初始化式只在构造函数的定义中而不是声明中指定

56、没有默认构造函数的类类型的成员、const或者引用类型的成员,都必须在构造函数初始化列表中进行初始化

57、构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序,成员被初始化的次序就是定义成员的次序,第一成员首先被初始化,然后是第二个,一次类推。

58、没有默认构造函数的类型,不能用于作动态分配数组的元素类型,静态分配数组必须为每个元素提供一个显式的初始化式

59、
C c();    //一个函数
C c;      //一个对象
C c = C();     //一个对象

60、
将构造函数声明为explicit,防止在需要隐式转换的上下文中使用构造函数。explicit关键字只能用于类内部的构造函数声明上,在类的定义体外部所做的定义上不再重复
除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit

61、
根据数据成员的声明次序来使用初始化式    C c={1, "2"};    //显式初始化
显式初始化类类型对象的成员有三个重大的缺点:
    (1)要求类的全体数据成员都是public
    (2)将初始化每一个对象的每一个成员的负担放在程序员身上
    (3)如果增加或删除一个成员,必须找到所有的初始化并正确更新

62、友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。友元的声明以关键字friend开始,它只能出现在类定义的内部,友元声明可以出现在类中的任何地方,友元不是授予友元关系的那个类的成员。

63、必须先定义包含成员函数的类,才能将成员函数设为友元。另一方面,不必预先声明类和非成员函数来将它们设为友元。

64、友元函数可以在类的内部定义,该函数的作用域扩展到包围该类定义的作用域

65、类必须将重载函数集中每一个希望设为友元的函数都声明为友元

66、
static成员函数不能被声明为const,毕竟成员函数声明为const就是承诺不会修改该函数所属对象,static函数也不能声明为虚函数。
static成员不是通过类构造函数进行初始化,而是应该在定义时进行初始化。
static关键字只能用于类定义体内部的声明中,定义不能标示为static。
const static数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义。
static数据成员可用作默认实参

67、容器中的元素总是按逆序撤销。

68、如果类需要析构函数,则它也需要赋值操作符和复制构造函数。赋值操作通常要做复制构造函数和析构函数也需要完成的工作

69、即使对象赋值给自己,赋值操作符的正确工作也非常重要。

70、重载操作符必须具有至少一个类类型或枚举类型的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。

71、重载操作符,操作符的优先级、结合性或操作数数目不能改变

72、除了函数调用操作符operator()之外,重载操作符时使用默认实参是非法的。

73、
大多数重载操作符可以定义为普通非成员函数或类的成员函数。
作为类成员的重载函数,其形参看起来比操作数数目少1。作为成员函数的操作符,有一个隐含的this形参,限定为第一个操作数。

74、操作符定义为非成员函数时,通常必须将它们设置为所操作类的友元。

75、内置逻辑与(&&)和逻辑或(||)操作符使用短路求值,如果重新定义该操作符,将失去操作符的短路求值特征。

76、重载逗号、取地址、逻辑与、逻辑或等操作通常不是好做法。

77、相同测试操作应使用operator==,测试对象是否为空的操作可用逻辑非操作符operator!表示

78、重载操作符指导规则:
    (1)赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误
    (2)像赋值一样,复合赋值操作符通常应定义为类的成员。与赋值不同的是,不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误
    (3)改变对象状态或者与给定类型紧密联系的其他一些操作符,如自曾、自减和解引用,通常应定义为类成员
    (4)对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。
79、一般而言,输出操作符应输出对象的内容,进行最小限度的格式化,它们不应该输出换行符

80、IO操作符必须为非成员函数,因为左操作数(第一个参数)必须为ostream类型

81、重载输入操作符必须处理错误和文件结束的可能性,可能发生的错误包括如下种类:
    (1)任何读操作都可能因为提供的值不正确而失败。
    (2)任何读入都可能碰到输入流中的文件结束或者其他一些错误
我们无需检查每次读入,只在使用读入数据之前检查一次即可。

82、后缀式操作符函数接受一个额外的int型形参。使用后缀式操作符时,编译器提供0作为这个形参的实参。这个形参不是后缀式操作符的正常工作所需的,它的唯一目的是使后缀函数和前缀函数区分开来

83、显示调用前缀和后缀操作符:
c.operator++(0);    //后缀    i++
c.operator++();       //前缀    ++i

84、operator转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。虽然转换函数不能指定返回类型,但是每个转换函数必须显式返回一个指定类型的值。转换函数一般不应该改变被转换的对象,因此,通常定义为const成员。
只允许一次类类型转换

85、已定义的类才可以用作基类,不可能从类自身派生出一个类

86、只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制

87、派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归

88、通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是派生类的版本中声明的值

89、派生列表中使用的访问标号决定该成员在派生类中的访问级别:
        如果是公用继承,基类成员保持自己的访问级别
        如果是受保护继承,基类的public和protected成员在派生类中为protected
        如果是私有继承,基类的所有成员在派生类中为private成员

90、在派生类中可以用using恢复基类的权限。例:
    class A: private B{
        public
            using Base::size;
    }

91、struct与class唯一的不同:默认成员保护级别和默认的派生保护级别

92、友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。

93、如果基类定义了static成员,则整个继承层次中只有一个这样的成员,无论从基类派生出多少个派生类,每个static成员只有一个实例。

94、可以使用派生类型的对象对基类类型的对象进行初始化或赋值,但没有从派生类型对象到基类类型对象的直接转换。

95、一个类只能初始化自己的直接基类。

96、如果派生类显式定义自己的复制构造函数或赋值操作符,则该定义将完全覆盖默认定义。被继承类的复制构造函数和赋值操作符负责对基类成分以及类自己的成员进行复制和赋值
       如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分。如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显示赋值
赋值操作必须防止自身赋值。

97、运行构造函数或析构函数的时候,对象都是不完整的,为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待
构造或析构期间的对象类型对虚函数的绑定有影响,如果在构造函数或析构函数中调用虚函数,则运行的是自身类型定义的版本

98、在继承情况下,派生类的作用域嵌套在基类作用域中。

99、与基类成员同名的派生类成员将屏蔽对基类成员的直接访问,使用作用域操作符访问被屏蔽成员

100、在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员,即使函数原型不用,基类成员也会被屏蔽

101、局部作用域中声明的函数不会重载全局作用域中定义的函数,同样,派生类中定义的函数也不会重载基类中定义的成员。通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类根本没有定义该函数时,才考虑基类函数。

102、如果派生类重定义了重载成员,则通过派生类型只能访问派生类中从定义的那些成员。如果派生类想通过自身类型使用所有的重载版本,则派生类必须重定义所有重载版本,要么一个也不定义。

103、派生类不要重定义所继承的每一个基类版本,可以为重载成员听过using声明。一个using声明只能指定一个名字,不能指定形参表,因此,为基类成员函数名称而作的using声明将该函数的所有重载实例加到派生类的作用域。将所有名字加入作用域之后,派生类只需要重定义本类型确实必须定义的那些函数,对其他版本可以使用继承的定义

104、函数调用遵循以下四个步骤:
    (1)首先确定进行函数调用的对象、引用或指针的静态类型
    (2)在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类,如果不能在类或者其相关基类中找到该名字,则调用是错误的。
    (3)一旦找到了该名字,就进行常规类型检查,查看如果给定找到的定义,该函数调用时候合法。
    (4)假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪一个函数版本,否则,编译器生成代码直接调用函数。

105、模板形参表不能为空,编译器将确定用什么类型替代每个类型形参,以及用什么值代替每个非类型形参。

106、
template<class T, size_t N> void array_init(T (&parm)[N]);
int x[10];
array_init(x); //将初始化x[10]

107、多个类型形参可以用作一个以上函数形参的类型。

108、类型转换的限制只适用于类型为模板形参的那些实参

109、显式模板实参从左到右与对应模板形参相匹配,第一个模板实参与第一个模板形参匹配...
template<class T1, class T2, class T3>
T1 sum(T2, T3);
long v3 = sum<long>(i, lng);

110、命名空间可以是不连续的

111、命名空间由它的分离定义部分的总和构成,命名空间是累积的。一个命名空间的分离部分可以分散在多个文件中,不同文本文件中的命名空间定义也是积累的。当然,名字只在声明名字的文件中可见。如果命名空间的一部分需要定义在另一文件中的名字,仍然需要声明该名字。

102、未命名的命名空间与其他命名空间不同,未命名的命名空间的定义局部于特定文件,从不跨越多个文本文件

103、未命名空间中定义的变量在成语开始时创建,在程序结束之前一直存在。未命名的命名空间中定义的名字只在包含该命名空间的文件中可见。如果在文件的最外层作用域中定义未命名的命名空间,那么,未命名的命名空间中的名字必须与全局作用域中定义的名字不同。

104、接受类类型形参的函数,以及与类本身定义在同一命名空间中的函数,在用类类型对象作为实参的时候是可见的

105、通常每个基类只初始化自己的直接基类,在应用虚基类的时候,这个初始化策略会失败。在虚派生中,由最底层派生类的构造函数初始化虚基类。

原文地址:https://www.cnblogs.com/swey/p/4058670.html