命名空间的using 声明

具体内容点击查看

作用域操作符 :: 表示编译器从操作符左侧名字所示的作用域中寻找右侧的名字。例如 std:cin 表示在使用命名空间std中的名字cin。

而使用using声明就无须在每个名字前加前缀了

1
2
3
#include <iostream>
using std::cin;
using std::cout;using std::endl;//一行可以放多条using声明

头文件中不应包含using声明,可能会产生名字冲突

标准库类型string

具体内容点击查看

定义和初始化string对象

初始化的几种方式
1
2
3
4
5
string s1;
string s2 = s1;//拷贝初始化
string s3("value");//直接初始化
string s3 = "value";//拷贝初始化
string s4(n, 'c');//直接初始化,把s4初始化为由连续的n个字符c组成的串

string对象上的操作

string对象上的常见操作
1
2
3
4
5
6
7
8
9
10
os<<s 将os写到输出流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 拷贝
s1 == s2 判断所含字符是否一致
s1 != s2

is>>s读取字符串,忽略开头的空白,从第一个字符读起,直至遇到下一个空白停止
getline(is,s)读取字符串,可以读取空格,直到遇到换行符为止。所以,如果想要保留输入时的空白符,使用getline(is, s)函数来读取字符串

string::size_type类型

当使用s.size()函数的时候,要特别关注size()函数的返回值的类型:string::size_type类型。string::size_type 类型是一个无符号类型,所以size()返回的是一个无符号整型数。

因为size()返回的是一个无符号整型数,所以要注意表达式中如果混用带符号数和无符号数将产生的结果。例如:

1
2
3
4
5
6
string str("haha");
int n = -1;
if(str.size() < n) cout<<"str.size() < n"<<endl;
else cout<<"str.size() >= n"<<endl;

//最终将会输出str.size() < n

因为str.size()是一个无符号数,负数n将自动转换成一个比较大的无符号数,原因见下方站内链接

string对象的比较
  1. 两个string对象大小的比较,先比较两者第一对相异字符
  2. 如果两者对应位置上字符都相同,长度长的大
  3. 如果长度和字符都相同,那么这两个string对象相等
1
2
3
4
5
6
string str1 = "Hello";
string str2 = "Happy";
string str3 = "hello";
string str4 = "Hello World";

//比较结果应该为 str3 > str4 > str1 > str2
字面值与string对象相加

当string对象和字面值相加的时候,必须保证+两侧的运算对象至少有一个是string

1
2
3
4
string s1 = "hello", s2 = "world";
string s3 = s1 + "," + s2;//正确
string s4 = "hello" + "," + s2;//错误,第一个加号左右两侧没有string
string s5 = s1 + "," + "world";//正确,一眼看去第二个加号左右好像没有string,实际上因为是从左到右计算,左侧的","类型被强制转换成了string,所以正确;

特别注意!由于某些原因,c++中字符串字面值与string是不同的类型,保证+两侧至少有一个是string的原因也是如此

处理string对象中的字符

string对象上的常见操作

使用标准库函数
头文件cctype中的函数含义
isalpha(c)当c为字母时为真
isdigit(c)当c为数字时为真
islower(c)当c为小写字母时为真
isupper(c)当c为大写字母时为真
ispunct(c)当c为标点符号时为真
isspace(c)当c为空白时为真
tolower(c)如果c是大写字母,输出对应的小写字母,否则原样输出c
toupper(c)如果c是小写字母,输出对应的大写字母,否则原样输出c

使用范围for语句

C++11新标准中,提供了范围for语句,语法形式为:

1
2
for(declaration : expression)
statement

使用范围for语句统计字符串中的字符

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <string>
using namespace std;

int main(){
string s("Hello, World!!!");
decltype(s.size()) punct_cnt = 0;//decltype读取s.size()返回的数据类型
for(auto c : s)
if(ispunct(c)) punct_cnt++;//ispunct(),当c为标点符号时返回true
cout<<punct_cnt<<endl;
return 0;
}

使用范围for语句改变字符串中的字符

1
2
3
4
string s("Hello World");
for(auto &c : s) //c为引用
c = toupper(c);
cout<<s<<endl;

要想改变string对象中字符的值,需要将循环变量定义成引用类型,因为引用只是对象的别名,所以改变引用变量实际上是通过引用直接改动了引用所绑定的对象

当然也可以通过下标运算符来操作string对象,这里不进行展开

标准库类型vector

具体内容点击查看

vector表示对象的集合,也被称为容器

c++中既有类模板又有函数模板,vector是一个类模板。编译器根据模板创建类或函数的过程称为类的实例化。

vector能容纳绝大多数对象,甚至包括vector

注意:在c++11之前,如果vector的元素还是vector,需要在外层vector的右尖括号与元素类型之间添加一个空格

​ 写成vector<vector<int> >

定义和初始化vector对象

具体内容点击查看
初始化vector对象的方法含义
vector<string> svec1默认初始化
vector<string> svec2(svec1)svec2中有svec1中所有元素的拷贝
vector<string> v1 = {"a", "an", "the"}列表初始化
vector<int> ivec(10, -1)10个int类型的元素,每个初始值都是-1
vector<string> v2{10}因为10不是string型,所以表示v2有10个默认初始化的元素
vector<string> v3{10, "hi"}同上,表示v3有10个值为”hi”的元素

注意:当使用花括号时,初始化过程会尽可能把花括号中的值当成是元素初始值的列表来处理,只有当无法执行列表初始化的时候才会考虑其他初始化方式

向vector对象中添加元素

具体内容点击查看

通常使用vector的成员函数push_back来执行添加元素的操作。

1
2
3
4
string word;
vector<string> text;
while(cin >> word)
text.push_back(word);

注意:vector对象不能通过下标形式添加元素,只能对确知已存在的元素指向下标操作

注意:vector对象能高效增长,相比于先指定容量后添加元素,先创建空的vector对象后添加元素的性能反而更好,这是vector的特性

其他vector操作

具体内容点击查看

vector的库函数操作与string类似,下面列出几个常用的操作

vector支持的操作含义
v.empty()如果v不含任何元素,返回true
v.size()返回v中元素的个数
v.push_back()向v的尾端添加一个元素

迭代器介绍

具体内容点击查看

除了可以通过下标运算符[]来访问string的对象的字符或vector对象的元素,还可以通过迭代器来实现同样的目的。

所有标准库容器都可以使用迭代器,但只有少数支持下标运算符。

string对象不属于容器类型,但它也支持迭代器。

使用迭代器

具体内容点击查看

使用begin成员返回第一个元素的迭代器

1
auto b = v.begin();

使用end成员返回“尾元素的下一位置”的迭代器,也称为尾后迭代器(尾迭代器)

1
auto e = v.end();

当容器为空,begin和end返回的都是尾后迭代器

迭代器运算符
迭代器的运算符含义
*iter返回迭代器iter所指元素的引用
iter->mem解引用iter并获取该元素名为mem的成员,等价于(*iter).mem
++iter让iter指示容器中的下一个元素
—iter让iter指示容器中的上一个元素
iter1 == iter2判断两个迭代器是否相等,如果两个迭代器指示的是同一个元素或
iter1 != iter2它们是同一个容器的尾迭代器,则相等;反之不相等
1
2
3
4
5
6
//使用迭代器实现将string对象第一个字母改成大写字母
string s("hello world");
if(s.begin() != s.end()){
auto it = s.begin();
*it = toupper(*it);
}

移动迭代器

使用迭代器将string对象中第一个单词改写成大写形式

1
2
3
string s("hello world");
for(auto iter = s.begin(); iter != s.end() && !isspace(*iter); iter++)
*iter = toupper(*iter);

泛型编程:并非所有的迭代器都有下标运算符,它们中的大多数都没有定义>或<运算符,但所有的标准容器的迭代器都定义了== 和!=。所以当我们需要进行判断的时候,最好使用迭代器和==/!=运算符,这样我们就不用纠结于容器类型

迭代器类型

迭代器的类型分为iterator和const_iterator

const_iterator 只能读取但不能修改所指的元素值;

iterator的对象可读可写;

当vector的对象或string的对象是一个常量,那么只能使用const_iterator。

begin和end运算符

通常来说,begin和end返回的具体类型由对象是否是常量决定,如果是常量,返回const_iterator,反之,返回iterator。

但有些时候,如果对象只需读操作而不需要写操作,那最好使用常量类型。

c++11新标准中引入了两个新函数,cbegin和cend,通过这两个函数得到的返回值一定是const_iterator类型。

1
auto iter = v.cbegin();

blue 结合解引用和成员访问操作

想要访问迭代器所指对象的成员,通常情况下需要先解引用再访问

1
2
(*iter).mem;
(*iter).empty();

除此之外,可以使用箭头运算符->来完成这两个操作

1
iter->mem;

例如,用迭代器实现:依次输出text的每一行直到遇到第一个空白行为止

1
2
3
for(auto iter = text.cbegin(); !iter->empty() && iter != text.cend(); iter++)
cout<<*iter<<endl;


注意:1、不能在范围for循环中向vector对象添加元素

​ 2、任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象的迭代器失效


</div>

迭代器运算

具体内容点击查看
迭代器的算术运算

可以令迭代器和一个整数相加或相减,其返回值是向前或向后移动了若干位置的迭代器

1
auto mid = vi.begin() + vi.size() / 2;//得到最接近vi中间元素的迭代器

还可以使用关系运算符(>,<,>=,<=)进行比较

1
if(it < mid)//处理前半部分的元素

也可以相减得到一个difference_type类型的带符号整型数,表示右侧的迭代器向前移动多少位置就能追上左侧的迭代器。

使用迭代器运算

使用迭代器可以实现二分查找

1
2
3
4
5
6
7
8
9
auto beg = v.begin();
auto end = v.end();
auto mid = beg + (end - beg) / 2;

while(mid != end && *mid != sought){
if(*mid > sought) end = mid;
else beg = mid + 1;
mid = beg + (end - beg) / 2;
}

注意:为什么mid = beg + (end - beg) / 2,而不是mid = (beg + end) /2?

1.指针和迭代器运算不支持相加运算。而相减得到一个difference_type类型的带符号整数,可以与迭代器相加。

2.beg + end这一步会有整数溢出的风险,相减更安全

数组

具体内容点击查看

定义和初始化内置数组

具体内容点击查看

1.数组的维度必须是一个常量表达式

2.字符数组的特殊性

字符数组有一种额外的初始化方式,使用字符串字面值初始化,要注意,字符串字面值的结尾还有一个空字符

1
2
3
char a1[] = {'C', '+', '+'};//列表初始化,维度为3
char a2[] = "C++"; //字符串字面值初始化,维度为4,因为还有一个空字符
char a3[6] = "Daniel"; //错误,因为没有空间存放空字符

3.不允许拷贝和赋值其他数组来初始化数组

4.理解复杂的数组声明

由内向外再由右向左阅读,更好理解

1
2
3
int *ptrs[10];				//ptrs是含有10个整型指针的数组
int (*Parray)[10] = *arr; //Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; //首先,arrRef是一个引用,然后发现它引用了一个大小为10的数组,最后看到数组中的元素是int

访问数组元素

具体内容点击查看

1.数组的访问方式和vector类似

2.要额外关注下标的值

具体内容查看c++primer p103,这里不进行展开

指针和数组

具体内容点击查看

数组名字通常会被编译器替换成指向数组首元素的指针

1
2
3
string nums[]{"i", "love", "you"};
string *p = nums;
string *p = &nums[0];//两者是等价的

指针也是迭代器

指针可以使用递增运算符来移动

1
2
3
int arr[]{0, 1, 2, 3, 4};
int *p = arr;
p++;

指针可以指向数组尾元素下一位置的指针

1
int *e = &arr[5];

可以用尾后指针重写遍历

1
2
for(int *b = arr; b != e; b++)
cout<<*b<<endl;

标准库函数begin和end

c++11新标准中引用了begin和end函数。但和迭代器的begin和end不同,使用形式应该将数组作为参数

1
2
3
int arr[] = {0, 1, 2, 3, 4};
int *beg = begin(arr);//指向首元素的指针
int *last = end(arr);//指向尾后指针

使用begin和end找到arr中的第一个负数

1
2
3
4
5
int arr[]{0, 1, 2, 3, 4, -5};
int *pbeg = begin(arr);
int *pend = end(arr);
while(pbeg != pend && *pbeg >= 0)
++pbeg;

指针运算

1
2
指针支持所有迭代器运算,包括解引用、递增、比较、与整数相加、两个指针相减等
两个指针相减返回的是一个ptrdiff_t带符号类型的结果

解引用和指针运算的交互

1
2
int ia[] = {0 ,2, 4, 6, 8};
int last = *(ia + 4);//last被初始化成8,等价于last = ia[4];

下标和指针

1
2
3
4
int ia[]{0, 1, 2, 3, 4};
int *p = &ia[2];//p指向ia数组下标为2的元素
int j = p[1];//p[1]等价于*(p + 1),j = ia[3];
int k = p[-2];//k = ia[0];

注意:数组内置的下标运算符不是无符号类型,可以是负数,这一点与vector和string不同

C风格字符串

具体内容点击查看

C风格字符串在c++程序中最好不要使用,string更加高效,更加安全

C标准string函数

C风格字符串的函数含义
strlen(p)返回p的长度
strcmp(p1, p2)比较p1,p2的大小,p1>p2,返回正值
p1 < p2 ,返回负值 相等返回0
strcat(p1, p2)将p2加到p1后,返回p1
strcpy(p1, p2)将p2拷贝给p1,返回p1

使用上述函数,输入的指针必须指向以空字符结束的数组

比较字符串

1
C风格字符串的比较必须使用strcmp函数

字符串的连接

1
C风格连接两个字符串,必须使用strcat,并且要保证存放的数组足够大,很容易出错

与旧代码的接口

具体内容点击查看

为了衔接旧时代没有string和vector的程序,c++提供了一些功能来进行衔接

混用string对象和C风格字符串

1
2
3
4
5
6
7
1.允许使用空字符结束的字符数组来初始化string对象或为其赋值
2.string加法运算中,允许其中一个运算对象为字符数组
3.string对象赋值运算中,允许字符数组作为右侧的运算对象

反之不成立
不过string提供了一个成员函数c_str来完成该功能
c_str返回的是一个c风格的字符串(以空字符结束)

可以使用数组初始化vector对象

1
2
3
4
int arr[]{0, 1, 2, 3, 4, 5};
vector<int> ivec(begin(arr), end(arr));

最终vector<int> ivec中将包括6个元素

现代的c++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;

尽量使用string,避免使用C风格的基于数组的字符串(字符数组)!

多维数组

具体内容点击查看

多维数组只不过是数组的数组

多维数组的初始化

1
2
3
4
5
6
int ia[4][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, {9, 10, 11}};
int ia[4][3] = {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};//显示地初始化第一行,其余元素执行默认初始化

多维数组的下标引用

1
2
3
int ia[2][3];
int (&row)[4] = ia[1];
//把row定义成一个含有4个整数的数组的引用,然后将其绑定到ia的第2行

使用范围for语句处理多维数组

1
2
3
4
5
6
7
8
9
10
size_t cnt = 0;
for(auto &row : ia){
for(auto &col : row){
col = cnt;
++cnt;
}
}
//必须要用引用类型作为循环控制变量
//外层循环使用引用的原因是:如果不用引用,编译器会将数组自动转成指向数组内首元素的指针,得到row的类型就成了int *,内层的循环就不合法了。
//内层循环使用引用的原因是:要修改数组中的值,所以使用引用类型。

指针和多维数组

1
2
3
4
多维数组实际上是数组的数组,多维数组名字转换成的指针实际上指向了第一个内层数组的指针
int ia[3][4];
int (*p)[4] = ia;//p指向了ia的首元素,一个含有四个整数的数组
p = &ia[2];//p指向ia的尾元素

通过使用c++11新标准中的auto或decltyp,遍历数组更方便

使用begin和end也可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//-------------------auto/decltype-------------------
for(auto p = ia; p != ia + 3; p++){
for(auto q = *p; q != *p + 4; q++){
cout << *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;
}

也可以使用类型别名简化多维数组的指针