====================================================================
day02
====================================================================
1.关于类型别名
(1)typedef double d; // d是double的别名
(2)using d = double; // c++11新标准,d是double的别名
注意问题:遇到一条使用了类型别名的声明语句,我们往往会尝试把类型别名替换为它原来的样子,这样是不对的。
eg: typedef char *pstring;
const pstring ctr = 0; // cstr是一个指向char的常量指针,pstring实际上是指向char的指针,
//因此const pstring是指向char的常量指针,而非指向常量字符的指针。
const char *cstr = 0; // 这一句是对const pstring cstr的错误理解,别名不是简单的替换。
2.类型说明符:
(1)auto类型说明符。使用条件:我们常常需要把表达式的值赋给一个变量,而这个表达式的结果的类型我们有时候并不知道,这时可以用auto。auto类型说明符。使用条件:我们常常需要把表达式的值赋给一个变量,而这个表达式的结果的类型我们有时候并不知道,这时可以用auto。
eg: auto item = val1+val2; // 由val1和val2相加的结果来推断item的类型。
(2)decltype类型说明符。使用条件:我们有时希望从表达式的类型推断出要定义的变量的类型,但并不想用该表达式的值来初始化变量,这时可以用decltype。
eg: decltype(f()) sum = x; // sum的类型就是函数f的返回类型。注意,编译器并不实际调用函数f()。
注意点:auto一般会忽略顶层const,而底层const则会保留下来。
const int ci = i,&cr = ci;
auto b = ci; // b是一个整数(ci的顶层const被忽略了)
auto c = cr; // c是一个整数(cr是ci的别名,而ci本身是一个顶层const,被忽略)
但是当将引用的类型设为auto,则保留顶层const。
eg: const int ci = i;
auto &h = ci; // h是一个整型常量引用,绑定到ci
而decltype处理顶层const和引用的方式与auto不同。
如果decltype使用的表达式是一个变量,则decltype返回表达式的类型(包括顶层const和引用在内)
const int ci = i,&cj = ci;
decltype(ci) x = 0; // x的类型是const int
decltype(cj) y = x; // y的类型是const int&,y绑定到常量
如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。
int i = 42,*p = &i,&r = i;
decltype(r+0) b; // 表达式的结果是int,因此b是一个int。 decltype(r)的结果是引用类型,
// 如果想让结果是r所指的类型,则可以把r作为表达式的一部分,如r+0
decltype(*p) c; // 表达式的内容是解引用操作,则decltype得到引用类型
decltype((i)) d; // 错误:变量加了一层或多层括号,编译器会把它当成一个表达式,因此d是int&,必须初始化。
注意:decltype((variable))的结果永远是引用!
赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果i是int,则表达式i=x的类型是int&
还有,decltype(&p)的结果int**,即一个指向整型指针的指针
还有一点应当注意:当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组,decltype则相反。
eg: int ia[] = {0,1,2,3};
auto ia2(ia); // ia2是一个整型指针,指向ia的第一个元素。
decltype(ia) ia3 ={0}; // ia3是一个含有4个整数的数组,ia有几个元素,ia3就含有几个。
=======================================================================
3.当向string中输入字符串时,string对象会忽略输入的空白符。若想保留输入的空白符,要用getline(cin,str);
eg: string line;
getline(cin,line);
4.string的size()函数的返回值是string::size_type,它是一个无符号类型的值,如果一条语句中已经有了size()函数就不要再使用int了,否则会带来混用无符号和有符号类型的问题。
如s.size()<n,如果n是一个具有负值的int,那么表达式的结果永远是true,因为有符号的值会转换为一个很大的无符号值。
5.处理string中的每一个字符,可使用范围for语句(range for):遍历给定序列中的每个元素,并对序列中的每个值执行某种操作。
for(declaration : expression)
statement;
eg: string str("some string");
for(auto c : str) // 对str中的每个字符c,执行打印操作。 注意:若想改变str中的元素,可以声明为auto &c
cout<<c;
6.若只想处理string中的部分字符,可以使用索引或者迭代器。
7.vector的列表初始化,如何知道括号中的值表示初始值还是元素数量?
如果用的是圆括号,可以说提供的值是用来构造vector对象的。
如果用的是花括号,表示我们想列表初始化该vector对象,初始化过程会尽可能地把花括号内的值当成是元素初始值的列表来处理。如果无法执行,才会考虑其他初始方式。
eg: vector<int> v1(10); // v1有10个元素,每个值都为0
vector<int> v2{10}; // v2有一个元素,值为10
vector<int> v3(10,1); // v3有10个元素,值为1
vector<int> v4{10,1}; // v4有两个元素,值为10和1
vector<string> v5{"hi"}; // v5有一个元素
vector<string> v6("hi"); // 错误
vector<string> v7{10}; // v7有10个默认初始化的元素
vector<string> v8{10,"hi"}; // v8有10个值为"hi"的元素
8.vector对象能高效增长,因此我们倾向于先定义一个空的vector对象,再在运行时向其中添加具体值。
需要注意的是:如果循环体内部有向vector对象添加元素的语句,则不能使用范围for循环(range for)。
9.vector可以通过下标访问元素,但不能通过下标添加元素,添加元素应该使用push_back()。
===================================================================================
10.迭代器。所有的标准库容器都可以使用迭代器,但只有少数几种才同时支持迭代器和下标操作符访问。迭代器类型有两种,iterator和const_iterator。迭代器用到begin和end运算符,它们的返回类型由对象是否为常量来决定。用法:
vector<type>::iterator b = v.begin();
vector<type>::iterator e = v.end();
或者:
auto b = v.begin();
auto e = v.end();
C++11标准引入了两个新函数,cbegin和cend,不论vector对象是否是常量,返回值都是const_iterator。
11.任何一种可能改变vector对象容量的操作,比如push_back(),都会使该vector对象的迭代器失效。
凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
12.需要注意,迭代器并未定义"+"操作,它只定义了"-"操作。
两个迭代器相减的结果是它们之间的距离,返回的是difference_type的带符号整型数。
所以,使用二分查找时,使用的是mid = beg + (end-beg)/2,而非mid = (beg+end)/2.
13.与vector相比,数组的缺点有:
(1)数组的维度必须是一个常量表达式,其大小运行过程中不会改变。
(2)数组不允许拷贝和赋值。也就是不能用别的数组来初始化。
(3)数组容易越界,而vector可以用迭代器等机制来控制。
14.指针也是迭代器。数组可以这样用:
eg: int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *e = &arr[10]; // 取arr尾元素下一位置的指针
for(int *b = arr;b != e;++b) // 打印数组中的元素
cout<<*b<<endl;
C++11新标准引入了两个名为begin和end的函数,这两个函数与容器中两个同名成员的功能类似。
这两个函数定义在iterator头文件中,用法:
eg: int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(arr);
int *last = end(arr);
15.初始化一个字符数组时,有两种方法,但它们有所区别:
eg: char a1[] = {'C','+','+'}; // 列表初始化,没有空字符
char a1[] = {'C','+','+',' '}; // 列表初始化,含有显式的空字符
char a3[] = "C++"; // 拷贝初始化,自动添加空字符!!!
C标准库里cstring头文件中有一些string函数,如strlen(p),strcpy(p1,p2)等,传入此类函数的指针必须指向以空字符作为结束的数组。
eg: char ca[] = {'C','+','+'};
cout<<strlen(ca)<<endl; //严重错误,ca没有以空字符结束,strlen()可能沿着ca在内存中的位置不断寻找,直到遇到空字符。
16.比较两个string变量可以直接比较:
eg: string s1 = "hello";
string s2 = "helloWorld";
if(s1<s2)....
而如果直接比较两个C风格字符串,实际上比较的是指针而非字符串本身。只能使用strcmp()
eg: const char ca1[] = "hello";
const char ca2[] = "helloWorld";
if(ca1<ca2) // 未定义的行为,试图比较两个无关的地址。(使用数组名的时候使用的其实是数组首元素的指针)
17.使用范围for循环处理多维数组时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。原因p114
eg: int ia[rowCnt][colCnt];
for(auto &row : ia) // 这一层必须为引用,否则编译无法通过。
for(auto col : row)
...
18.可以使用数组初始化vector对象
eg: int a[] = {0,1,2,3,4,5};
vector<int> ivec(begin(a),end(a));
或者vector<int> ivec(a+1,a+4); //使用a[1]、a[2]、a[3]初始化ivec