-
可以使用
using
声明而无需专门的前缀:using namespace::name;
.。位于头文件的代码一般来说不应该使用using
声明,这是因为头文件的内容会拷贝到所有引用他的文件中去,如果头文件中有某个using
声明,那么每个使用了该头文件的文件都会有这个声明。对于某些程序来说,由于不经意间包含了一些名字,反而可能产生始料未及的名字冲突。 -
使用
string
类型需要添加头文件#include<string>
,并且要对string
进行声明:using std::string;
-
string
类型的初始化:string s1; 默认初始化,s1是一个空字符串 string s2 = s1; s2是s1的副本,等价于s2(s1) string s3 = "hello"; s3是该字符串字面值的副本,等价于s3("hello"); string s4(10, 'c'); s4的内容是10个c
需要注意的是
s3
是的内容不包括字面值最后的空字符。当初始值只有一个的时候,拷贝初始化(使用=
)和直接初始化都可以,但是如果有多个初始值(s4
的初始化),就只能够使用直接初始化的方式。 -
string
的操作os<<s 将s写入都输出流os中,返回os is>>s 从is中读取字符串赋给s,字符串以空白分隔,返回is getline(is, s) 从is中读取一行赋给s,返回is s.empty() s为空返回true,否则返回false s.size() 返回s中字符的个数 s[n] 返回s中第n个字符的引用 s1+s2 返回s1和s2连接后的结果 s1=s2 用s2的副本代替s1中原本的字符 s1==s2 如果s1和s2所包含的字符完全一样,则返回true s1!=s2 <,<=,>,>= 根据字典序进行比较
可以使用
while(cin>>s)
进行循环读入,getline
会读入换行符,但是不会把换行符保存到string
对象中。因为getline
返回的也是输入流,因此也可以使用while(getline(cin,s))
进行循环读入 -
size()
函数返回值是string::size_type
类型,这是一个无符号整型,因此如果我们直接使用size()
的返回值和一个负数比较,则几乎肯定是true
,这是因为负数n
会自动转换成一个比较大的无符号整数。(在C++中int
和unsigned int
在一起的时候前者会自动转换为后者)。尽量还是用一个int
变量保存返回值或者进行强制类型转换。 -
如果两个字符串前面都相同,长的那个比短的大
-
复合运算符
+=
负责把右侧string
对象追加到左侧string
对象的后面 -
当把
string
对象和字符(串)字面值混在一条语句中使用时,必须确保每个加法运算符+
两侧的运算对象至少有一个是string
。string a = "Hello"; string b = a + "world" + "!"; //正确 string c = "Hello" + "world" + a; //错误
字符串字面值与
string
是不同的类型 -
应该尽可能使用C++版本的C标准库头文件(去掉结尾的
.h
,在前面加上c
表示属于C标准库的头文件),虽然两者内容一样,但是在名为cname
中的头文件中定义的名字从属于命名空间std
,而.h
头文件则不然。 -
可以使用库
cctype
判断一个字符的类别isalnum(c) 当c为字母或数字为真 isalpha(c) 当c为字母为真 iscntrl(c) 当c为控制字符为真 isdigit(c) 当c为数字为真 islower(c) 当c为小写字母为真 ispunct(c) 当c为标点符号为真 isspace(c) 当c为空白字符为真 isupper(c) 当c为大写字母为真 tolower(c) 如果c是大写字母则返回小写字母,否则原样输出 toupper(c) 如果c是小写字母则返回大写字母,否则原样输出
-
如果想要改变
string
对象中字符的值则必须把循环变量定义成引用类型:string s; cin >> s; //将s中每个字母变成大写 for(auto &c : s) {c = toupper(c); } cout << s << endl;
从这里也可以看出
c
每次会重新定义 -
string
类型的下标是string::size_type
类型的,任何一个带符号类型的值将自动转化为这种无符号类型。注意不要越界,越界的行为是未定义的。 -
注意逻辑运算符
&&
和||
的短路性质 -
const string
的每一个字符是const char
类型的 -
模板本身不是类或者函数,可以将模板看做编译器生成类或者函数编写的一份说明。编译器根据模板创建类或函数的过程成为实例化 。当使用模板时,需要指出编译器应把类或函数实例化成何种类型。
vector<int> a; vector<vector<string>> file;
vector
能容纳绝大多数类型的对象作为元素,但是因为引用不是对象,所以不存在包含引用的vector
。
需要指出的是,在旧版本中多维vector
的声明中最后的>
之间必须有空格,否则编译器会认为是右移运算符。 -
vector
初始化:vector<T> v1; v1是一个空的vector,潜在的元素是T类型的,执行默认初始化 vector<T> v2(v1); v2包含有v1所有元素的副本 vector<T> v2 = v1; 等价于v2(v1) vector<T> v3(n, val)v3包含了n个重复元素,每个元素的值都是val vector<T> v4(n) v4包含了n个重复地执行了值初始化的对象 vector<T> v5{a, b, c,...}v5包含了初始值个数的元素,每个元素被赋予对应的初始值 vector<T> v5 = {a, b, c...}等价于v5{a, b, c,...}
-
C++几种初始化方式一般来说可以相互等价地使用,但是也有几种特殊情况:
- 使用拷贝初始化(使用
=
的时候)只能提供一个初始值 - 类内初始化只能使用拷贝初始化或花括号的形式
- 如果提供的是初始元素之的列表,则只能把初始值放在花括号里而不能放在圆括号里
- 使用拷贝初始化(使用
-
可以只提供
vector
对象容纳的元素数量而略去初始值,此时库会创建一个值初始化的元素初值,并把它赋给容器中的所有元素。但是如果元素对象是一个类,而这个类明确要求需要提供初始值,则无法完成初始化工作。 -
如果使用圆括号来初始化,可以说提供的值是用来构造
vector
对象的;如果使用花括号,可以表述成我们想列表初始化该vector
对象。但是如果提供的值不支持列表初始化,编译器就会尝试理解为直接初始化(把{
当做(
来处理),如果仍旧无法进行初始化,则报错 -
使用
push_back
方法向vector
的尾部添加元素。C++标准要求vector
应该能在运行时高效添加元素,因为vector
对象能够高效地增长,在定义vector
对象设定其大小没有什么必要,事实上这么做性能可能更差,这一点和Java
不同。 -
如果循环体内部包含向
vector
对象添加元素的语句,则不能使用范围for
循环(for(auto item:a)
) -
常见的
vector
操作:v.empty() v.size() v.push_back(t) v[n] v1 = v2 用v2中元素的拷贝替换v1中的元素 v1 = {a,b,c...} 用列表中元素的拷贝替换v1中的元素 v1 == v2 v1和v2相等当且仅当他们的元素数量相同而且对应位置的元素值都相同 <, <=, >, >= 顾名思义,以字典顺序进行比较
-
size
函数返回size_type
类型,需要指定对应的模板类型,例如:vector<int>::size_type //正确 vector::size_type //错误
使用比较运算符需要元素可以相互比较
vector
的下标运算符可用于访问已经存在的元素,而不能用于添加元素 -
所有标准库容器都可以使用迭代器,但是其中只有少数几种才同事支持下标运算符。严格来说,
string
对象不属于容器类型,但是string
支持很多与容器类型类似的操作。这些类型都拥有名为begin
和end
的成员,其中begin
成员负责指向第一个元素的迭代器,end
成员负责返回指向容器尾元素的下一位置的迭代器(尾后迭代器或尾迭代器)。如果容器为空,begin
和end
返回同一个迭代器。使用auto
进行迭代器的定义比较方便。
常见操作:*iter 返回迭代器所指元素的引用 iter->member 相当于(*iter).member ++iter 令迭代器指向容器的下一个元素 --iter 令迭代器指向容器的上一个元素 iter1 == iter2 判断两个迭代器是否相等 iter1 != iter2
试图解引一个尾后迭代器是未定义的行为
//以此处理s的字符串直到我们全部处理完或遇到空白字符 for(auto it = s.begin(); it != s.end() && !isspace(*it); ++it) {*it = toupper(*it); }
-
在C++中我们更愿意使用迭代器和
!=
判断是否到达尾部。这样做的原因是所有容器都支持这些操作,而下标和<
则只有很少的容器定义。 -
迭代器的类型有两种:
iterator
和const_iterator
,后者表示不能修改元素值,如果vector
或string
是const
的,则只能使用这种类型。string::iterator it1; vector<int>::const_iterator it2;
如果容器是常量,则
begin
和end
返回的类型是const_iterator
类型的迭代器。如果我们使用cbegin
和cend
则无论容器是否是常量都返回const_iterator
类型的迭代器。//依次输出text的每一行直到遇到第一个空白行 for(auto it = text.cbegin(); it != text.cend() && !it->empty(); ++it) {cout << *it << endl;3 }
-
使用范围
for
循环或者使用迭代器进行循环都不能改变容器对象的容量,否则有可能使得迭代器失效 -
迭代器运算:
string
和vector
的迭代器提供了更多的运算符,可以让迭代器每次跨过多个元素,另外也支持迭代器进行关系运算//小心越界 iter + n 迭代器向后移动n个元素 iter - n 迭代器向前移动n个元素 iter += n iter -= niter1 - iter2 返回两个迭代器之间的距离 <, <=, >, >=
其中迭代器之间的
-
返回difference_type
的带符号整数int bSearch(const vector<int> &a, int x) { auto begin = a.cbegin();auto end = a.cend();auto mid = begin + (end - begin) / 2;while(mid != end && *mid != x){if(*mid < x)begin = mid + 1;else end = mid;mid = begin + (end - begin) / 2;}if(mid == end) return -1;else return mid - begin; }
-
数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问,不过数组的大小不能改变。
-
数组是一种复合类型,元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的(维度必须是一个常量表达式)。数组的元素会被默认初始化,如果数组的元素是内置类型而且数组在函数内部定义,则每个元素不会被初始化。不允许用
auto
关键字由初始值的列表推断类型,且不存在引用的数组 -
如果用初始值列表初始化数组,则数组的维度可以不指明,默认为初始值列表的长度。如果指定维度的话则维度至少要比初始值列表的长度长,剩下的元素会被默认初始化
-
字符数组可以用字符串字面值进行初始化,不过需要注意的是字符串字面值的末尾有一个空字符也会被拷贝到字符数组中,因此字符数组的长度应该是可见的字符个数加一
-
不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值
-
对于声明中有括号的应该先理解括号里面的,再从右往左进行理解
int *ptr[10]; ptr是含有10个整型指针的数组 int &ptr[10]; 错误,不存在引用的数组 int (*ptr)[10]; ptr是指针,指向含有10个元素的数组 int (&ptr)[10] = arr; ptr是引用,指向一个含有10个元素的数组
-
在使用数组下标时,通常将其定义为
size_t
了类型。size_t
是一种机器相关的无符号类型,被设计得足够大能够表示内存中任意对象的大小。在cstddef
头文件中定义 -
对于数组我们同样可以使用范围
for
循环:unsigned scores[11] = {}; //进行初始化 for (auto i : scores)cout << i << " ";
-
在C++中,使用指针的时候编译器一般会把它转换成指针。在很多用到指针名字的地方,编译器都会自动将其替换为一个指向数组首元素的指针。
-
当我们将数组作为一个
auto
变量的初始值时,推断得到的类型是指针而非数组。但是用decltype
返回的类型仍然是数组int ia[] = {0, 1, 2, 3, 4}; auto ia2(ia); //ia2是一个整型指针 decltype(ia) ia3 = {}; //含有5个元素的整型数组
-
我们可以像使用迭代器一样使用指针,为了让指针的使用更简单、安全,C++11新标准引入两个名为
begin
和end
的函数。这两个函数定义在iterator
头文件中int a[10] = {}; int *beg = begin(a); //相当于int *beg = a; int *last = end(a); //相当于int *last = &a[10];
同尾后迭代器一样,尾后指针不能执行解引操作和递增操作
-
迭代器支持的操作指针都支持,包括和整数进行加减、指针和指针的减法、指针和指针比较大小,但是需要注意的是上面的操作两个指针要在同一个数组中才有意义。特别地,允许给空指针加上或减去一个值为0 的整型常量表达式。两个空指针也循序彼此相减,结果当然是0。
-
指针同时有解引和其他运算的时候最好加上括号标明运算顺序
-
标准库类型限定使用的下标必须是无符号类型,而内置的下表运算无此要求
-
尽管C++支持C风格字符串,但在C++程序中最好还是不要使用他们,这是因为C风格字符串不仅使用起来不太方便,而且极容易引发程序漏洞,是诸多安全问题的根本原因。
-
C风格字符串存放在字符数组中并以空字符结束。
-
在头文件
cstring
中有一些操作C风格字符串的函数:strlen(p) 返回p的长度 strcmp(p1, p2) 比较p1和p2,如果p1==p2,返回0,如果p1<p2,返回负数,否则返回正数 strcat(p1, p2) 将p2附加到p1之后,返回p1 strcpy(p1, p2) 将p2拷贝给p1,返回p1
传入此类函数的指针必须指向以空字符作为结束的数组
-
任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代:
- 允许使用以空字符结束的字符数组来初始化
string
对象或为string
对象赋值 - 在
string
对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个对象都是),在string
对象的复合赋值运算中允许使用以空白字符结尾的字符数组作为右侧的运算对象
- 允许使用以空字符结束的字符数组来初始化
-
string
对象的成员函数c_str()
返回值是一个C风格的字符串,也就是说,函数的返回值是一个指针, 该指针指向一个以空字符结束的字符数组,而这个数组所存的数据恰好与那个string
对象一样。结果指针的类型是const char*
,从而确保我们不会改变字符数组的内容。我们无法保证c_str()
函数返回的数组一直有效,事实上,如果后续的操作改变了s的值就可能让之前返回的数组失去作用。如果执行完c_str()
函数后程序想一直都能使用其返回的字符数组,最好将该数组重新拷贝一份。const char *str = s.c_str();
-
允许使用数组来初始化
vector
对象,只需要指明拷贝区域的首元素地址和尾后地址即可。int int_arr[] = {0, 1, 2, 3}; vector<int> a(begin(int_arr), end(int_arr));
-
现代的C++程序应当尽量使用
vector
和迭代器,避免使用内置数组和指针;应该尽量使用string
,避免使用C风格的基于数组的字符串 -
C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。
-
多维数组的初始化
int ia[3][4] = {{0, 1, 2, 3}.{4, 5, 6, 7},{8, 9, 10, 11} }; int ia[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; int ia[3][4] = { {0}, {4}, {8} }; 只初始化每行的首元素,其他未列出的元素执行默认初始化 int ia[3][4] = {0, 3, 6, 9}; 只初始化第一行的元素,其他的初始化为0
多维数组的操作:
int (&row)[4] = ia[1]; //把row绑定到ia的第二个4元素数组上 int cnt = 0; for (auto &row : ia) {for( auto &col : row) {col = cnt++;} }
注意:要使用范围
for
循环处理多维数组,除了最内层的循环外,其他所有循环控制变量都应该是引用类型。如果不是引用类型,编译器的会把数组类型转化成指针类型,这样内层循环就无法使用范围for
语句了,因为对一个指针进行循环显然没有意义。 -
当程序使用多维数组的名字时,也会自动将其转换成指向数组首元素的指针。因为多维数组实际上是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针。
int ia[3][4] int (*p)[4] = ia; //p指向含有4个整数的数组ia[0] p = &ia[2]; //p指向ia的尾元素ia[2]
上面的括号必不可少,如果没有括号,
int *p[4]
的意义将会是大小为4的整型指针数组 -
随着C++11新标准的提出,通过使用
auto
或者decltype
就能尽可能避免在数组前面加上一个指针类型了。int a[3][4]; for (auto p = ia; p != ia + 3; ++p) { //int (*p)[4]for (auto q = *p; q != p + 4; ++q) {//int *qcout << *q << ' ';}cout << endl; }
可以使用标准库函数
begin
和end
实现同样的功能:for (auto p = begin(ia); p != end(ia); ++p) {for (auto q = begin(*p); q != end(*p); ++q) {cout << *q << ' ';}cout << endl; }
可以使用类型别名简化多维数组的指针
using int_arr = int[4]; typedef int int_arr[4]; //同上面等价+
-
第三章看完啦,啦啦啦啦,后面要稍微加快进度