C++ Primer 笔记(1)

还是应该认真地学一点C++啊……以下记录我在看书的过程中认为值得记下的东西,请大牛指教。前面几章因为内容比较熟悉了,记得就比较简略。

Chapter 1

在这句话 cout<<“hello”<<endl; 里,<<是一个操作符,它左边的操作数是一个ostream对象(在本例中为std::cout),运算的结果是左边的值,也就是还是cout,上面那句话等价于 (cout<<“hello”)<<endl;

把std::endl写入ostream时,会输出一个换行,并刷新缓冲区。在用printf或是cout打log进行调试时,应该意识到缓冲区的存在,如果没有及时刷新的话,真正显示到屏幕上的时刻未必就是输出语句执行的时刻。

Chapter 2

表达式可以分为lvalue和rvalue两种,lvalue只指可以放在赋值符号左边的表达式,lvalue可以使用的范围比rvalue更广。下面语句是不合法的 (a+b)=12,因为(a+b)不是lvalue。

C++有两种初始化变量的方式:直接初始化和复制初始化。两种方式分别如下所示

int loli(14);
int loli2 = 15;

初始化不是赋值,虽然第二句看上去是赋值,但它们是不同的。在内置类型(比如int, double, char之类)上效果上虽然没什么区别,但如果是类类型,可能就会有很大的不同(以后详述)。

引用(reference)实际上就是变量的一个别名,它只能在初始化的时候绑定到另一个变量上,并且以后不能更改。类似这个样子 int loli2 = &loli; int &loli2 = loli。通过这个引用修改变量,会导致访问原来的变量时也是修改后的值。

const引用是指通过这个引用只能读取而不能修改其绑定到的对象。如果一个对象是const的,那么不能把通常的引用绑定上去(因为这样在语义上就可以通过这个引用对本来是const的对象进行修改了),只能绑定const引用。另外,因为不可修改,const引用也可以绑定到右值上,比如const int &loli = 14是合法的,而 int &loli2 = 15不合法。

关于变量/函数的定义与声明,以及头文件的相关内容,我以前写过两篇Blog()。

Chapter 3

为了避免名字冲突,C++里引入了命名空间(namespace)的概念,通常来讲要使用一个名字,必须指明它所在的命名空间。比如标准库里的东西都在std这个里面,在使用的时候应该std::cin, std::vector<std::string>等等。但是我们往往为了省事,直接在程序前面用using语句,比如我用了 using lolispace::loliname 这样的语句,那么以后用到loliname这个名字时,就不需要写成lolispace::loliname这样了。但是如果我们要用到lolispace里另外一个名字叫做anotherloli的,就还需要在前面加一条using lolispace::anotherloli 。更偷懒的办法是直接用一条using namespace lolispace完全包括。因为标准库里的内容都是在std里,这也就是我们常常写一个using namespace std在最前面的原因。不过如果你还用到了别的命名空间的话,这种粗暴写法是很有可能导致冲突的。

另外,注意不要在头文件里用using语句,在头文件里一定要用完整的名字,因为你不知道你的头文件会被谁include,乱写的话就污染了人家的命名空间。

记录一个string的长度的安全方法是用string::size_type类型,而不是直接用int(我觉得这个不大可能真的会出问题,不过编译器往往会提出警告)。还有STL里的各种容器的大小也一样。

在C++里如果要使用原来C语言里的函数,推荐使用<cname>系列的头文件。比如cstdio, cstring, cmath, cctype等等。一个好处是这里面定义的名字都是在std命名空间里,这就与C++的库保持了一致。

vector<int>::const_iterator 和 const vector<int>::iterator 是完全不一样的。前者是一个“只读”迭代器,它指向的位置可变,但是不能通过它修改内容。后者是一个不能动的可读可写的迭代器,只能在初始化的时候指定位置。后者基本上不可能出现在实际情况中。

Chapter 4

在定义数组时,数组的维数必须是一个const对象,且在编译期就能确定下它的值。所以下面的写法是错误的(虽然在大多数编译器上可以通过):

int N = 10;
int arr[N];

在当C++课助教时发现有无数的孩子这么写。另外插一句,C99的标准允许这样写,所以目前来说这是正确的C程序,不正确的C++程序。

局部数组是不会进行默认初始化的,除非你显式地指定初值。比如 int local_array[5] = {1,2,3,4,5}。如果后面花括号里的值比数组的维数小,则后面的部分以默认方式初始化(int置0,string置空串等等),所以初始化一个全零的局部数组的简便写法是 int local_array[MAX] = {0}。

直接用字符串字面值(literal)初始化一个字符数组时,注意最后有一个。如果这样写 char str[] = “loli”; 那么str的维数是5而不是4.

const int *p表示不能通过p修改p指向的内容,但p本身的值可变(可以指向其它地方),而 int *const p表示p的值不可变,它指向一个int,并且可以通过p修改int的值。

用new动态分配了数组后,一定要用delete[]删除,不要漏了那个中括号。

Chapter 5

优先使用前置自增(减)操作符,比如++x。因为后置操作符(x++)必须先保存操作数原来的值以便在表达式结束时返回原来的值。如果操作数是基本类型,影响微乎其微。但如果操作数是一个重载了自增操作符的复杂的类类型对象,保存中间结果这一步的花费可能就很可观了。

C++语言里,&&, ||, ?: 以及逗号操作符明确规定了求值顺序。对于&&,先计算左侧的值,如果为假则不再计算右侧的值。对于||,如果左侧为值则不再计算右侧。这两个被称为所谓“短路(short-circuit)求值”。对于三元操作符?:,会根据问号左侧的结果选择某一个分支求值,另一分支不再计算。对于逗号,规定从左到右依次求值。对于其他的操作符,两侧操作数的求值顺序标准里没有规定,取决于具体的编译器实现,比如要计算 f1() * f2(),我们不知道这两个函数到底哪个先被调用。所以你现在可以痛骂某些脑残笔试题了。

C++里的强制转换有四种,static_cast, dynamic_cast, const_cast, reinterpret_cast。dynamic_cast涉及多态等内容,留待后叙。const_cast是负责强制去除对象的const属性。static_cast负责通常的强制转化,比如把double截断成int(如果没有显示转化的话编译器通常会给warning),把一个void*转换成一个有类型的指针之类。reinterpret_cast是一个更加暴力的逐位转化,比如下面代码

int *ip;
char *pc = reinterpret_cast<char*>(ip);

把一个整数强行解释成一个字符数组。一定要慎用强制转换,通常来说dynamic_cast和static_cast有少数时候确实要用;如果发现非要用const_cast不可,往往是你的设计出了问题;reinterpret_cast不要用。当然C语言里的强制转换是最剽悍的,把这些全部通吃,非常霸气。XD

  1. 引用那个 int loli2 = &loli; 应该是int& loli2 = loli;吧

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>