导言
程序设计语言中充满陷阱,一不小心就会掉入其中万劫不复,之所以有陷阱,是因为语言的设计细节不符合程序员的直觉
所以你会发现,语言越高级越注重顺从程序员的直觉。
c++也有许多陷阱,所谓山不过来,我就过去,因此将c++中易错点、难点集合于此,会不定期更新。
字符串与vector
字符串字面值与标准库string不是同一种类型
string s("hello"); cout<<s.size()<<endl; //OK cout<<"hello".size()<<endl; //ERROR cout<<s+"world"<<endl; //OK cout<<"hello"+"world"<<endl; //ERROR
strlen、sizeof与size()求字符串长度的区别
cout<<strlen("123")<<endl; //返回 3 cout<<sizeof("123")<<endl; //返回 4 string s = "123"; cout<<s.size()<<endl; //返回 3
标准string库中的getline函数返回时会丢弃换行符
const iterator与const_iterator的区别
vector<int>::const_iterator //不能改变指向的值,自身的值可以改变 const vector<int>::iterator //可以改变指向的值,自身的值不能改变 const vector<int>::const_iterator //自身的值和指向的值都是只读的
任何改变vector长度的操作都会使已存在的迭代器失效。如:在调用push_back之后,就不能再信赖指向vector的迭代器了
vector<int> ivec; ivec.push_back(10); vector<int>::iterator it = ivec.begin(); cout<<*it<<endl; ivec.push_back(9); cout<<*it<<endl; //迭代器已经失效
数组与指针
字符数组除了可以用花括号在定义时初始化外,还可以用字符串字面值初始化,但谨记字符串字面值包含一个额外的空字符
char c1[] = {'h','e','l','l','o'}; char c2[] = "hello"; cout<<sizeof(c1)/sizeof(char)<<endl; //长度是5 cout<<sizeof(c2)/sizeof(char)<<endl; //长度是6
一个数组不能用另一个数组初始化,也不能将一个数组赋值给另一个数组
int a[3] = {1,2,3}; int b[3][3] = {{1,2,3},{1,2,3},{1,2,3}}; //right int c[3][3] = {a,a,a}; //error
若指针保存0值,表明它不指向任何对象。但是把int型变量赋值给指针是非法的,尽管此int型变量的值可能为0
int a = 0; int *p1 = 0; //right int *p2 = a; //error
typedef string *pstring; const pstring cstr;
cstr的类型是 string * const 还是 const string * ?
答:是string *const cstr,而非 const string *cstr。容易产生误解的原因是const限定符既可以放在类型前也可以放在类型后,const pstring cstr等价于pstring const cstr。遇到此类问题时,把const放在类型之后来理解。
区分:int *ip[4] 和 int (*ip)[4]
第一个表示一个数组,元素是int指针
第二个表示一个指针,指向int数组,遇到此类问题时,由内向外读。
运算符
除法/和求模%
若两个操作数是正数,则除法的结果是正数,求模的结果也是正数
若两个操作数是负数,则除法的结果是正数,求模的结果是负数
若只有一个操作数是负数,则除法和求模的结果取决于机器,除法可以确定结果是负数
逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算其右操作数,只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数
区分 if(i<j<k) 和 if(i<j && j<k)
第一个i<j或者为0或者为1,只要k大于1,表达式就为true
第二个必须i<j且j<k表达式才为true
区分 if(val) 和 if(val == true)
第一个只要val非零则表达式为true,val为0则表达式为false
第二个只有val为1表达式为true,val非1则表达式为false
int val = 2; if(val==true){ //不会进入if cout<<"val==true"<<endl; }
多个赋值操作符中,各对象必须具有相同的数据类型,或者具有可转换为同一类型的数据类型。
int ival; int *pval; ival = pval = 0; //error 尽管ival和pval都可以赋值为0 string s1,s2; s1 = s2 = "OK" //ok
如果指针指向不是用new分配的内存地址,则在该指针上使用delete是不合法的。
通常编译器不能断定一个指针是否指向动态对象,因此尽管这样做是错误的,但在大部分编译器上仍能运行通过,但是会产生运行时错误。
整形提升
对于所有比int小的整形(char, signed char, unsigned char, short, unsigned short),如过该类型所有可能值都包含在int中,他们会被提升为int型,否则,他们将被提升为unsigned int。
对于包含signed和unsigned int型的表达式,表达式中的signed型整数会被转换为unsigned型。
int i = -5; unsigned int ii = 1; cout<<(i>ii)<<endl; //输出1,非常有趣的结果 原因是int型的i转换为unsigned int型
short i = -5; unsigned short ii = 1; cout<<(i>ii)<<endl; //输出0 比较时short和unsigned short都提升为int型