1.++i要比i++效率高。
原因:i++多了一个保存i自增之前副本的操作。
2.单纯的对变量运算不会改变变量的值
假设一段代码
int i=7;
i-2;
这样的语句不会对i的值进行任何的改变,只是用i进行了运算而已,如果想改变i的值,则需要这样写:
i=i-2;
类似的还有:
i<<1;//表示i向量的二进制向左移动一位
只是这样写的话,i的值不会改变,要想改变,必须:
i=i<<1;
3.逻辑运算符(&&,||)和位运算符(&,|)的优先级比关系运算符(!=,==,<=)低.
所以以下表达式中位运算符需要加括号:
if(!(a&b)=0)
4.常用转义字符
\n 换行符
\r 回车(与换行的区别是,回车会使光标回到本行开头,擦拭掉本行之前输出的东西,重新开始输出)
\b 退格键(Backspace键)
\0 空字符
’ 单撇号
‘’ 双撇号
\t 制表(一般默认输入8个空格)
5.声明与定义的区别
声明指出了变量的类型和名字,并不申请储存空间和分配初始值。
定义除了规定变量的类型和名字外,还申请了储存空间,并有可能分配初始值。除非特别用关键词extern修饰,如extern int i,并且没有显式初始化,这样的属于声明; 其他的方式均为定义,如 int j; extern int k=3;
6.static 关键字的作用
- 隐藏:加了关键字后,变量和函数只能在当前文件中使用。
- 延时:生存期的延长,变量在静态储存区,生存期为整个程序源文件。
- 默认初始化:默认为0;
- 类成员声明为static后,它成为类独立于对象的成员,只有一个副本,没有this指针。
7.默认初始化时,内置类型的值未被定义,而类会使用默认构造函数
默认构造函数的存在,是由于类内的数据成员都给了默认值或者可以默认初始化
8.头文件中不能包含变量和函数的定义,除了三个例外:类,const变量,inline函数。
头文件可能会被多个cpp文件包含,如果函数和变量在头文件中定义,它在每个cpp文件里都会被定义一次,这样会造成重复定义,在编译时不会出错,但是在将各个cpp文件链接起来时,就会报错。
所以如果一个函数或变量想在多个cpp文件共享的话,只需要在一个cpp里定义,然后再头文件中声明就行,这样其他cpp文件只需要include头文件就能使用这个变量或函数。
9.要解引用一个指针之前,一定要确认这个指针是非空的,否则会出错
*和->都需要先检查对象是否为空。
举个栗子:
if(p->val&&p!=NULL),这个表达式就是错的,因为&&首先判断左边的值,所以会先执行p->val,这时如果p是NULL,就会报错。
正确的写法应该是if(p!=NULL&&p->val)
10.如果要返回一个值表示程序出错了,返回的值一定要确定是正常结果不会出现的值。
11.size_type是一个unsigned int 的类型,它无法直接和int等有符号数比较,但是可以把size_type赋值给int,这样会进行隐式转换。
例如: vectorvec={1};
int i=vec.size();
注意:size_type 类型的如果小于0则会报错。
12.当返回值为bool的函数没有设置return语句时,程序不会报错,而是返回不确定的值。
13.sizeof函数形参是数组名字时,得到的是整个数组占的内存大小,是指针时,得到的是这个指针的大小。
例如:
char a[10];
char *p=a;
cout<<sizeof(a)<<" "<<sizeof§<<endl;
输出的结果将会是
10 4
14.定义在main函数中的对象如果没有初始化,也而且也不是一个类对象(拥有默认构造函数),其值是未定义的,使用时会产生错误。定义在main函数外的对象,会被默认初始化为0。
15.一个变量要想传入一个函数来改变它,要么作为一个返回值返回,要么作为引用传递给函数。普通的值传递改变的只是变量的副本。
16.size_type 如果想和int一起比较的话,需要加上(int)转为int类型再比较
17.map.insert()当关键词在字典中时,什么也不会做,即使关键字对应的值不一样,也不会更新。
如果想做到,当关键词不在map时插入,在时更新对应的值,需要用到map的下表操作。
18.类内定义vector并初始化
例如class Try中定义vector
应该这么做:vector< int>vec;
然后在Try的构造函数中初始化vec:
Try():vec(10,0){};
19.一个空类,sizeof为1
20.多态只能通过virtual来实现,当通过基类指针调用的派生类对象的函数不是虚函数时,不会有多态的发生,只会调用基类的对应函数。
21.explicit:的作用,阻止隐式转换
具体来说,当类的构造函数是单个参数时,我们在需要用到类的地方,可以直接用这个形参类型的实参来代替,会用该实参自动生成一个临时的类对象,然后使用,最后再释放掉。
虽然这样很方便,但是如果我们用临时对象的数据成员会出错时,就要避免这种用法,比如,当我们用了临时对象中初始化为0的数做除数时,就会出错,就不能用这种隐式初始化的方式,需要手动初始化以更改要做除数的默认值。
22. 异常处理,throw by value,catch by reference
遵循throw by value,catch by reference的原则,catch by reference是为了实现多态,以用基类的引用处理所有派生类的对象,为什么不用指针,因为异常通常是临时对象,如果是很容易出现指针空悬,或在堆上的话,不知道什么时候手动delete,十分麻烦。
23.应该尽量避免在析构函数中的异常
析构函数是释放对象内存的函数,如果还没释放完就throw了一个异常,则会有内存的泄漏。
但如果析构函数中的操作可能出现错误怎么办?两个办法
1.打掉牙往肚里咽,即在析构函数中catch这个异常,处理掉它
2.或者在析构函数里用判断语句,把异常转移个外部的其他函数。
24.共有继承是is-a,私有继承或包含是has-a
is-a:香蕉是一种水果。
has-a:午餐有一种东西是水果。
25.inline函数
内联的函数需要在调用处展开,所以需要在调用时能看到完整的定义。
不适合内联的几种情况:
1.构造函数和析构函数会产生大量代码,不适合内联
2.包含了递归和循环等结构的函数不适合内联
3.函数中包含其他函数的调用的不适合内联
4.虚函数一般不内联
26.析构函数中调用delete this 会无限递归
而在其他成员函数中嗲用delete this 不会出错,只是所有涉及this指针的操作都无效了
27.C++智能指针的实现
:一个类,两个数据成员,一个对象指针和一个引用计数指针。
构造函数接收一个对象指针,赋给智能指针类的对象指针,引用指针赋值为(new int(1));
拷贝构造函数:引用计数加一
拷贝赋值运算符:左边的引用计数减一,如果减到0了,则释放掉两个数据成员。然后再把右边的复制给坐边,引用赋值加一
28.C++构造函数什么时候用初始化列表
需要知道,初始化列表是直接初始化了数据成员,而一般的初始化则是赋值,这是关键的区别
三种情况必须用:
1.数据成员为引用&,不能没有绑定就赋值,必须用初始化来绑定。
2.数据成员为const,不能赋值,只能初始化
3.数据成员没有默认构造函数,则无法默认初始化,只能在初始化列表里初始化
一种情况用了最好,会提高性能:
当数据成员为有默认构造函数的类时,这时用普通的构造函数,会出现两次构造,一次时数据成员的默认构造,一次是构造函数中的拷贝赋值,这时用初始化列表就会只调用一次拷贝构造函数。
29.字符串拷贝函数strcpy
当内存重叠时,倒着拷贝
30.dynamic_cast
当从派生类的指针或引用往基类转时,一定可以转成功
当从基类指针或引用往派生类转时,如果这个指针或引用指向的本身就是派生类,那么就可以转,否则如果这个指针指的本身是一个基类的对象,那么是转化不成派生类指针的。
可以扩大,不能缩小
31.atoi()函数把c风格字符串转为数字,定义在头文件<stdlib.h>中
32.C/C++语言输入输出函数:
C输出函数:
fprintf,printf。区别是,printf直接输出到屏幕上,fprintf输出到文件流,比如文档中,也可以输出到屏幕上,因此fprintf多了一个指定输出位置的参数。
C输入函数:
fscanf,scanf:fscanf从文件流输入,scanf是键盘输入,两个都是遇到空格符或换行符结束。
fgets:与fscanf的区别是读一行数据,遇到空格不结束,遇到换行符结束,同时保留换行符。
两个函数都会在末尾加一个空字符’\0’。
C++输入函数
cin:标准输入,遇到空格或换行符结束。
getline:读取一行数据,遇到换行符\n结束。
特别注意:如果getline之前有一个cin,那么cin的结束标志\n将会被getline捕捉到,因此getline将会无效,解决办法是,在cin后面,再加一个getline或cin.get(),消化掉这个\n。
33.cout<<“a”<<ends;是在末尾输出一个‘\0’,如果这个cout是在一个循环中时,可能会影响下一次输出。
cout<<“a”;则是只输出a,末尾什么都没有。