C++ Primer阅读心得(第四章、第五章)
2008-01-14 22:45
218 查看
1.运算符重载时,我们可以重新定义已存在运算符的行为,但是无法修改运算对象的个数、运算符的优先级和结合律。
2.在C++中存在左值(lvalue)和右值(rvalue)的区别:左值是地址,可以出现在赋值语句的左边和右边(当它出现在右边时,实际上使用的是它的值);右值是变量的值,只可以出现在赋值语句的右边。c++11中新增的decltype作用于得到左值的表达式(注意不是变量)时返回的是引用类型,而decltype作用于右值时得到的是普通的类型。
&&(逻辑与):短路规则,只有在左侧为true时才对右侧求值;这个运算符的运算对象和返回值都是右值。
||(逻辑或):短路规则,只有在左侧为false时才对右侧求值;这个运算符的运算对象和返回值都是右值。
?:(条件运算符):先求条件表达式,再根据结果对后面的两个表达式之一进行求值;根据 : 两侧的表达式来决定返回值类型:都是左值,返回左值,否则返回右值。
, (逗号运算符):先求左边再求右边;它返回右侧表达式的计算结果(右侧返回左值则返回左值,反之返回右值)。
8.对于内置类型来说,前置递增++递减--,与后置递增++递减--,在只执行变量自增1或者自减1的运行效率上是一致的。但要注意在对复杂迭代器的处理上,后置++或者--效率要比前置版本低很多。所以,如非必要,在使用迭代器时,推荐使用前置版本。
9.在bitmap或者simhash中,我们要用到位操作,下面列一下常用位操作:
某位置1:
某位置0:
检查某位:
计算1的总数:
10.关于左移<<和右移>>,这里再扯个淡。c++语法规范中规定了,当右操作数大于做操作数的bit数时,运算结果是无法预测的。在intel处理器的SHL和SHR中,左移右移的计数器(counter)只有5bit(32位)或者6bit(64位),当你左移或者右移大于32或者64时,会造成计数器溢出,得到意料之外的结果。
11.sizeof操作符返回size_t类型的常量表达式(所以可以作为数组的维度),它并不实际计算运算对象的值(与decltype很像啊)。所以sizeof一个无效指针(未初始化或者已被delete)的解引用是安全的,能够返回正确的值。sizeof以一个byte作为1,对象占用几个byte就返回几。在c++11中,允许sizeof直接作用于类的成员,而不需要实现实例化一个类的对象。
对于整型:char/signed char/unsigned char/short/int 先被提升为int;如果unsigned short能被int装下,则unsigned short也被转换为int,否则unsigned short和int都被转换为unsigned int;如果表达式中包含unsigned int型,则表达式被提升为unsigned int型,如果表达式中包含long型,则表达式被提升为long型,如果表达式中有unsigned long,则表达式被提升为unsigned long型;如果unsigned
int能被long装下,则unsigned int也会被提升为long型,否则unsigned int和long都会被提升为unsigned long型。
对于浮点型:如果表达式中包含float型,则表达式被提升为float型,如果表达式中包含double型,则表达式被提升为double型。
13.其他类型的隐式转换:
数组名转换为首元素的指针
0或者nullptr转换为任意类型的指针;任意类型的指针转换为void *类型
非空指针或者非零算术值转换为true,空指针或者零算术值转换为false
非常量指针或者引用转换为常量指针或者引用(反之则不行)
类类型的自动转换(需要重载操作符)
14.显示转换四个操作符的区别:
static_cast:静态转换,相当于c中的类型转换,编译过程中转换不成功将报错
dynamic_cast:动态转换,具有运行时类型检查的特性,只能被使用在将变量转化为类的指针/类的引用/void*时,进行父类子类之间的现实转换时更加安全
const_cast:去掉变量的const/volatile属性
reinterpret_cast:重解释转换,可以在任何两种类型之间进行转换,而不必担心编译器的检查,由程序员自己负起转换后正确使用的责任
15.空语句:只包含一个;(分号)的语句称为空语句,要注意循环语句和条件语句后面的空语句,它们容易造成逻辑上的错误而且难以发现。(血的教训啊...)
16.复合语句:使用{}包括起来的一段语句块,被视为一个语句,它内部的变量具有块作用域。所以可以使用块语句扩展if/else/while/for/do等等后面的statement语句,达到多做很多事的目的。(原来还有这么深的道理,我一直以为if等语句后面跟这个{}是天然的呢.......汗!)
17.悬垂else:悬垂else是指,在同一语句作用域内,每个else都将与它最接近的if匹配,这有些时候会造成逻辑上的错误(与程序员预想不一致),在每个if和else之后使用{},划分清楚作用域即可解决此问题。
18.关于switch语句:switch后面的()中只能放置结果为整型的表达式;case后面只能紧跟常量表达式(constexpr出场);执行了一个case之后,如果没有break,C++将执行下一个case直到switch结束或者遇到break为止;只能在switch的最后(default或者最后一个case)中定义变量,如果想在这之前定义变量,请在该变量的外面添加{}表示语句块,否则当程序未执行到此case时,会导致这个变量在后面的语句中出现未定义的错误。
19.注意:在for语句头初始化的变量只在循环内部可见。
需要注意三点:
可以在范围for中声明引用,然后使用此引用来修改序列中的值。(好东西,比Java自由一些)
如果不声明为引用,那么语句中声明的临时变量是序列中元素的副本,修改它不会影响到序列本身。在序列的元素很占用空间的情况下,可以使用const &来实现只读加减少复制(节省空间)的效果。
在范围for语句中,不要修改序列本身(新插入一个元素或者删除一个元素),因为这会导致范围for持有的序列end()迭代器失效,进而造成越界访问等问题。
21.do while语句与while和for相比,能够让循环体先执行一次再判断条件。但是要注意在do后面的语句块中定义的变量,在while的()中是不可见的。最后说一个奇葩的do while(0)吧,这个语句有两个作用:
替代goto语句:假设我们有个函数要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,在退出前先释放资源,那么可以用do while(0)代替goto。
在定义了多条语句的宏定义中使用,防止宏定义被if/else/while等语句拆开:
22.break语句和continue语句与悬垂else具有同样的工作原理,与距离最近的while/for/switch等语句匹配,注意不要产生逻辑上的错误。
23.异常的处理过程:异常发生之后,首先在本函数中查找是否有匹配的catch语句,如果没有,那么终止本函数,向上在调用这个函数的函数中查找,如果还没有则终止调用的函数继续向上...如果一直找到顶层(main)都没有匹配的catch,则调用terminate函数终止整个程序。
2.在C++中存在左值(lvalue)和右值(rvalue)的区别:左值是地址,可以出现在赋值语句的左边和右边(当它出现在右边时,实际上使用的是它的值);右值是变量的值,只可以出现在赋值语句的右边。c++11中新增的decltype作用于得到左值的表达式(注意不是变量)时返回的是引用类型,而decltype作用于右值时得到的是普通的类型。
int a, b=10; a = 5; //a左值,5右值 a = b; //a左值,b左值(转化为右值10) decltype(a) c; //int型,a是变量 decltype((a)) d; //int &型,(a)是一个表达式,返回a左值3.除了&&(逻辑与)、||(逻辑或)、? :(问号运算符)和 ,(逗号运算符)之外,c++中其他运算符没有规定运算对象的求值顺序,所以如果一个子表达式修改了另外一个子表达式的变量,那么运算结果无法预计,应当予以避免。
int i = 0; cout<<i<<" "<<++i<<endl; //i和++i不确定谁先执行,输出"0 1"和"1 1"都有可能4.c++11中改进了结果为负数的商取整的方式,规定一律向0取整。同时也规定了当%(求模)的两个操作数一正一负时的行为,让它等于两个绝对值求模之后再加上一个负号。
int d = -20.1/3; //d向0取整,等于-6 d = -20%-3; //都是负数,得到-2 d = -20%3; //等价于d=-(20%3),得到-25.赋值操作符是右结合的,而算术运算符是左结合的。例如:
a+b+c 就是(a+b)+c 而a=b=c 就是a=(b=c)6.列表赋值:c++11中允许使用{}括起来的初始值列表作为赋值语句的右侧运算对象。(注意内置类型列表初始化不能损失精度)
int k = {3.14}; //错误,精度损失 vector<int> v; v = {1,2,3}; //ok7.具有求值顺序的操作符:
&&(逻辑与):短路规则,只有在左侧为true时才对右侧求值;这个运算符的运算对象和返回值都是右值。
||(逻辑或):短路规则,只有在左侧为false时才对右侧求值;这个运算符的运算对象和返回值都是右值。
?:(条件运算符):先求条件表达式,再根据结果对后面的两个表达式之一进行求值;根据 : 两侧的表达式来决定返回值类型:都是左值,返回左值,否则返回右值。
, (逗号运算符):先求左边再求右边;它返回右侧表达式的计算结果(右侧返回左值则返回左值,反之返回右值)。
8.对于内置类型来说,前置递增++递减--,与后置递增++递减--,在只执行变量自增1或者自减1的运行效率上是一致的。但要注意在对复杂迭代器的处理上,后置++或者--效率要比前置版本低很多。所以,如非必要,在使用迭代器时,推荐使用前置版本。
9.在bitmap或者simhash中,我们要用到位操作,下面列一下常用位操作:
某位置1:
result |= 1<<x; //原数字与0..010..0或,利用或的特性保留其他位的值,只将第x位置1
某位置0:
result &= ~(1<<x); //原数字与1..101..1与,利用与的特性保留其他位的值,只将第x位设置为0;对0..010..0求反刚好得到1..101..1
检查某位:
status= result & (1<<x); //原数字与0..010..0与,利用与的特性去掉其他位的值,只保留第x位
计算1的总数:
int cnt = 0; while(result) { result &= result - 1; ++cnt; }
10.关于左移<<和右移>>,这里再扯个淡。c++语法规范中规定了,当右操作数大于做操作数的bit数时,运算结果是无法预测的。在intel处理器的SHL和SHR中,左移右移的计数器(counter)只有5bit(32位)或者6bit(64位),当你左移或者右移大于32或者64时,会造成计数器溢出,得到意料之外的结果。
int operand_r = 32; unsigned int i = 1 << operand_r; //因为处理器的左移计数器溢出,所以相当于:1<<0,i等于1,而不是unsigned int溢出之后的0
11.sizeof操作符返回size_t类型的常量表达式(所以可以作为数组的维度),它并不实际计算运算对象的值(与decltype很像啊)。所以sizeof一个无效指针(未初始化或者已被delete)的解引用是安全的,能够返回正确的值。sizeof以一个byte作为1,对象占用几个byte就返回几。在c++11中,允许sizeof直接作用于类的成员,而不需要实现实例化一个类的对象。
char c; sizeof c; //返回1,一个byte嘛 char &rc = c; sizeof rc; //返回被引用值的大小,1 char *p; sizeof p; //返回指针的大小,一般为一个int,4 sizeof *p; //返回被指向对象的大小,为初始化也没关系,1 char ca[16] = {}; sizeof ca; //返回数组的大小,16 string s; sizeof s; //返回固定部分大小,4 vector v; sizeof v; //返回固定部分大小,12 sizeof myclass::member; //c++11新增,方便了 int ia[16] = {0}; size_t length = sizeof(ia)/sizeof(*ia); //length = 16,利用sizeof特性计算数组长度12.内部算数类型隐式转换规则:
对于整型:char/signed char/unsigned char/short/int 先被提升为int;如果unsigned short能被int装下,则unsigned short也被转换为int,否则unsigned short和int都被转换为unsigned int;如果表达式中包含unsigned int型,则表达式被提升为unsigned int型,如果表达式中包含long型,则表达式被提升为long型,如果表达式中有unsigned long,则表达式被提升为unsigned long型;如果unsigned
int能被long装下,则unsigned int也会被提升为long型,否则unsigned int和long都会被提升为unsigned long型。
对于浮点型:如果表达式中包含float型,则表达式被提升为float型,如果表达式中包含double型,则表达式被提升为double型。
13.其他类型的隐式转换:
数组名转换为首元素的指针
0或者nullptr转换为任意类型的指针;任意类型的指针转换为void *类型
非空指针或者非零算术值转换为true,空指针或者零算术值转换为false
非常量指针或者引用转换为常量指针或者引用(反之则不行)
类类型的自动转换(需要重载操作符)
14.显示转换四个操作符的区别:
static_cast:静态转换,相当于c中的类型转换,编译过程中转换不成功将报错
dynamic_cast:动态转换,具有运行时类型检查的特性,只能被使用在将变量转化为类的指针/类的引用/void*时,进行父类子类之间的现实转换时更加安全
const_cast:去掉变量的const/volatile属性
reinterpret_cast:重解释转换,可以在任何两种类型之间进行转换,而不必担心编译器的检查,由程序员自己负起转换后正确使用的责任
15.空语句:只包含一个;(分号)的语句称为空语句,要注意循环语句和条件语句后面的空语句,它们容易造成逻辑上的错误而且难以发现。(血的教训啊...)
int i = 0; while(i!=10); //错误的空语句,导致死循环 ++i; if (i > 9); //错误的空语句,导致后续statment总是执行 cout<<i<<endl;
16.复合语句:使用{}包括起来的一段语句块,被视为一个语句,它内部的变量具有块作用域。所以可以使用块语句扩展if/else/while/for/do等等后面的statement语句,达到多做很多事的目的。(原来还有这么深的道理,我一直以为if等语句后面跟这个{}是天然的呢.......汗!)
17.悬垂else:悬垂else是指,在同一语句作用域内,每个else都将与它最接近的if匹配,这有些时候会造成逻辑上的错误(与程序员预想不一致),在每个if和else之后使用{},划分清楚作用域即可解决此问题。
18.关于switch语句:switch后面的()中只能放置结果为整型的表达式;case后面只能紧跟常量表达式(constexpr出场);执行了一个case之后,如果没有break,C++将执行下一个case直到switch结束或者遇到break为止;只能在switch的最后(default或者最后一个case)中定义变量,如果想在这之前定义变量,请在该变量的外面添加{}表示语句块,否则当程序未执行到此case时,会导致这个变量在后面的语句中出现未定义的错误。
19.注意:在for语句头初始化的变量只在循环内部可见。
for(int i=0; i<10; i++) //do sth int k = i; //错误,i出了作用域,已被销毁20.c++11中引入了范围for语句(类似于Java中的for_each),来简化for循环的编写。
for (auto element : sequence) do sth
需要注意三点:
可以在范围for中声明引用,然后使用此引用来修改序列中的值。(好东西,比Java自由一些)
string s = "abcd"; for (auto &c : s) c = toupper(c);
如果不声明为引用,那么语句中声明的临时变量是序列中元素的副本,修改它不会影响到序列本身。在序列的元素很占用空间的情况下,可以使用const &来实现只读加减少复制(节省空间)的效果。
vector<string> v = {"abcd","efgh"}; for (const auto &e : v) cout<<e;
在范围for语句中,不要修改序列本身(新插入一个元素或者删除一个元素),因为这会导致范围for持有的序列end()迭代器失效,进而造成越界访问等问题。
21.do while语句与while和for相比,能够让循环体先执行一次再判断条件。但是要注意在do后面的语句块中定义的变量,在while的()中是不可见的。最后说一个奇葩的do while(0)吧,这个语句有两个作用:
替代goto语句:假设我们有个函数要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,在退出前先释放资源,那么可以用do while(0)代替goto。
bool dosth() bool dosth() { { thing *p = new thing(); thing *p = new thing(); bool bRet = true; bool bRet = true; do{ // 执行并进行错误处理 // 执行并进行错误处理 bRet = func1(); bRet = func1(); if(!bRet) goto errorhandle; if(!bRet) break; bRet = func2(); bRet = func2(); if(!bRet) goto errorhandle; if(!bRet) break; // processing....... // processing...... // 执行成功,释放资源并返回 // 执行成功,释放资源并返回 delete p; delete p; p = NULL; p = NULL; return true; return true; errorhandle: }while(0); delete p; delete p; p = NULL; p = NULL; return false; return false; } }
在定义了多条语句的宏定义中使用,防止宏定义被if/else/while等语句拆开:
#define SAFE_DELETE(p) delete p; p = NULL; if(NULL != p) SAFE_DELETE(p) //逻辑错误,SAFE_DELETE只被执行了一半,另外一半被扔到了if外边,必定执行 //改成: #define SAFE_DELETE(p) {delete p; p = NULL} if(NULL != p) SAFE_DELETE(p); //语法错误,{}后面跟着;无法通过编译 //改成: #define SAFE_DELETE(p) do{ delete p; p = NULL} while(0) //就可以避免以上的错误,既不会被拆开后面多个;也能正确执行
22.break语句和continue语句与悬垂else具有同样的工作原理,与距离最近的while/for/switch等语句匹配,注意不要产生逻辑上的错误。
23.异常的处理过程:异常发生之后,首先在本函数中查找是否有匹配的catch语句,如果没有,那么终止本函数,向上在调用这个函数的函数中查找,如果还没有则终止调用的函数继续向上...如果一直找到顶层(main)都没有匹配的catch,则调用terminate函数终止整个程序。
相关文章推荐
- C++ Primer阅读心得(第一章、第二章上)
- 阅读 《大规模并行处理器程序设计》影印版心得 第四章 CUDA Threads
- C++ Primer阅读心得(第六章)
- C++ Primer阅读心得(第八章、第九章)
- C++ Primer阅读心得(第十四章)
- 阅读《软件工程—理论方法与实践》第四章心得体会
- C++ Primer阅读心得(第三章)
- C++ Primer阅读心得(第十二章)
- 第四章&第五章 表达式和语句学习笔记(第一遍阅读)
- C++ Primer阅读心得(第十章、第十一章)
- C++ Primer阅读心得(第十五章)
- C++ Primer阅读心得(第十六章)
- C++ primer阅读心得(第十三章)
- 阅读《软件工程—理论方法与实践》第五章心得体会
- C++ Primer阅读心得(第二章下)
- C++ Primer阅读心得(第七章)
- C++ Primer 第二遍阅读笔记(第四章)
- C++ Primer学习心得第四章
- 阅读 《大规模并行处理器程序设计》影印版心得 第五章 CUDA Memories
- C++ Primer 阅读记录之继承类的构造函数