C++ Primer 学习笔记 第三章 字符串、向量和数组

using声明后就可以不使用作用域操作符了,如:

std::cin;
// 等价于:
using std::cin;    // 声明后再用cin时就不用加前缀了
cin;

每个using声明引入命名空间中的一个成员,但也可以指定变量所在的命名空间:

using namespace std;

头文件不应包含using声明,因为头文件内容会拷贝到所有引用了它的文件中,那么每个使用了该头文件的文件都会有这个声明,有时会产生名字冲突。

标准库类型string表示可变长的字符序列,使用前要先包含string头文件,string定义在命名空间std中。

初始化string方式:

string s1;    // 默认初始化,s1是空字符串
string s2 = s1;    // s2是s1的副本
string s3 = "hiya";    // s3是该字面值去掉最后的'\0'的副本
string s4(10,'c');    // s4是cccccccccc
string s5(s1);    // 与s2方式初始化方式等价
string s6("hiya");    // 与s3初始化方式等价

拷贝初始化:用=初始化
直接初始化:不用=初始化

string s1 = "hiya";    // 拷贝初始化
string s2("hiya");    // 直接初始化
string s3(10,'c');    // 直接初始化
string s4 = string(10,'c');    // 拷贝初始化,它实际上是创建了一个临时对象用于拷贝

读写string对象:

string s;
cin >> s;    // 将string对象读入s,从第一个非空白(空格符、换行符、制表符等)开始,遇到空白停止

读取未知数量的string对象:

string word;
while (cin >> word){    // 遇到EOF(windows下为Ctrl+Z、linux下为Ctrl+D)或非法输入后返回false
    cout << word << endl;   
}

用getline函数读取一整行:

string line;
while (getline(cin, line))    // 遇到EOF(windows下为Ctrl+Z、linux下为Ctrl+D)或非法输入时返回false
    cout << line << endl;    // 因为没有换行符,所以用endl换行并刷新显示缓冲区

函数getline(is,s)从输入流is中选取一行(换行符也被选取进来了)存入string类型对象s中(不存换行符,换行符被舍弃)。

string的empty和size操作:

s.empty();    // 字符串s为空时返回true,否则返回false
string::size_type size = s.size();    // 返回字符串s的长度

size函数返回的是string::size_type类型的值,大多数标准库类型都定义了几种配套的类型,如size_type,体现了与机器无关的特性。具体使用时通过作用域操作符表明size_type是在类string中定义的。string::size_type类型是个无符号值且能存放下任何string对象的大小。

在C++11新标准中,可以使用auto自动推断size操作的返回值类型:

auto size = s.size();    // size的类型为string::size_type

由于size函数返回的是无符号整型数,如在表达式中混用带符号数和无符号数会产生意想不到的结果(int可能转化为无符号数):

string::size_type size = 666666666666;
int a = -1;
if (size < a){
    cout << a;    // a会被输出,因为string::size_type一般位数比int大,因此int会转化为size的类型,a的值就变为2^size的位数次方-1
}

而以下代码:

unsigned char size = 6;
int a = -1;
if (size < a){
    cout << a;    // a不会被输出,因为size会被提升为int类型,6<-1为假
}

比较string对象:

string str = "Hello";
string phrase = "Hello World";
string slang = "Hiya";

slang>phrase>str(按字典顺序排)。

string赋值操作:

string st1(10,'c'), st2;
st1 = st2;    // 两者都变成了空串

两个string对象相加:

string s1 = "hello, ", s2 = "world\n";
string s3 = s1 + s2;    // s3内容为hello, world\n
s1 += s2;    // 等价于s1 = s1 + s2;

字面值与string类型对象相加:

string s1 = "sssss";
string s2 = s1 + "1";    // 1必须加双引号表示字符串,s2变为sssss1
string s3 = "sss" + "dsdsd";    // 错误,必须确保每个+运算符两侧至少有一个string类型对象
string s4 = s1 + "sss" + "dsdsd";    // 正确,s1+"sss"整体的类型为string
string s5 = "sss" + "dsdsd" + s1;    // 错误,"sss"+"dsdsd"两端类型都不是string,加号的结合律是从左往右的

string与字符串字面值类型不同,字符串字面值的类型为const char数组。

C++兼容C的标准库,C中头文件name.h在C++中命名为cname。在名为cname的头文件中定义的名字属于命名空间std,定义在.h中的不属于。

在cctype头文件中定义了一组标准库函数检查和改变某个字符的特性:
函数名称 返回值
isalnum() 如果参数是字母或者数字,函数返回true
isalpha() 如果参数是字母,函数返回true
iscntrl() 如果参数是控制字符,函数返回true
isdigit() 如果参数是数字(0-9),函数返回true
isgraph() 如果参数是除空格之外的打印字符,函数返回true
islower() 如果参数是小写字母,函数返回true
isprint() 如果参数是打印字符(包括空格),函数返回true
ispunct() 如果参数是标点符号,函数返回true
isspace() 如果参数是标准空白字符,如空格、换行符、水平或垂直制表符、进纸符,函数返回true
isupper() 如果参数是大写字母,函数返回true
isxdigit() 如果参数是十六进制数字,即0-9、a-f、A-F,函数返回true
tolower() 如果参数是大写字符,返回其小写,否则返回该参数
toupper() 如果参数是小写字符,返回其大写,否则返回该参数

C++11新标准提供了范围for语句,可以遍历给定序列中的每个元素并对序列中的每个值执行某种操作:

for (declaration : expression){
    statement
}

一个string对象表示一个字符的序列,因此string对象可以作为范围for语句中expression部分:

// 把str中每个字符分行输出
string str("some string");
for (auto c : str){
    cout << c << endl;
}

若想改变字符串中字符的值,需要用到引用:

// 将字符串字符变为大写
string str("some string");
for (auto &c : str){
    c = toupper(c);
}

若只想处理一部分字符,可以用下标运算符[ ],下标运算符接收的是string::size_type类型的值,表示要访问的字符的位置,返回值是该位置上字符的引用,string对象的下标从0开始,s.size() - 1结束。

在用下标访问string类型值之前要先判断是否为空串,空串的0下标也是未定义的。可用s.size()来判断,若返回值为0说明是空串。

逻辑与运算符&&只有在左侧运算对象为真时才会检查右侧对象。

标准库类型vector表示对象的集合,其中所有对象的类型都相同,集合中每个对象都有一个与之对应的索引,常用于访问对象。vector容纳着其他对象,也常被称为容器。vector包含在头文件vector中。

vector是一个类模板。模板本身不是类或函数,相反可以看做编译器生成类或函数的一份说明,编译器根据模板创建类或函数的过程称为实例化,使用模板时,需要指出编译器应把类或函数实例化成何种类型。

对于类模板,我们通过提供一些信息来指定模板到底实例化成什么样的类,需要提供哪些信息由模板决定,提供信息的方式总是这样:在模板名字后面跟一对尖括号,其中放上信息,以vector为例:

vector<int> ivec;    // ivec保存int类型对象
vector<Sales_item> Sales_vec;    // 保存Sales_item对象
vector<vector<string>> file;    // 该向量的元素是vector对象(C++11标准)
vector<vector<string> > file;    // 该向量的元素是vector对象(旧标准)

定义和初始化vector对象:

vector<string> svec;    // 默认初始化,svec不含任何元素
vector<int> ivec;
vector<int> ivec2(ivec);    // 将ivec的元素拷贝给ivec2
vector<int> ivec3 = ivec;    // 等价于上句代码
vector<string> svec(ivec2);    // 错误,ivec2的类型与svec不符

C++11新标准提供了列表初始化vector对象的方法:

vector<string> articles = {"a", "an", "the"};    // articles对象包含三个字符串元素

C++提供了几种初始化方式,大多数情况下可以等价使用,但:
(1)使用拷贝初始化时,只能提供一个初始值。
(2)如果提供的是一个类内初始值,则只能使用拷贝初始化或使用花括号形式初始化。
(3)如果提供的是初始元素值的列表,则只能把初始值都放在花括号里进行列表初始化,而不能放在圆括号里:

vector<string> v1{"a", "an", "the"};    // 列表初始化
vector<string> v1("a", "an", "the");    // 错误

创建指定数量的元素:

vector<int> ivec(10, -1);    // 10个int类型元素,都被初始化为-1
vector<string> svec(10, "hi!");    // 同上

只提供vector对象容纳的元素数量而略去初始值,此时库会创建一个值初始化的元素初值,这个初值由元素类型决定,如果是内置类型,自动初始化为0,如果是类类型,则由类默认初始化:

vector<int> ivec(10);    // 10个int元素,每个初始化为0
vector<string> svec(10);    // 10个string元素,每个初始化为空串

如果某种类类型不支持默认初始化,则不能使用上述方法默认初始化。以上只提供了元素数量而不设初始值,其中元素只能使用直接初始化而不能使用拷贝初始化。

列表初始值还是元素数量:

vector<int> v1(10);    // v1有10个元素,默认初始化为0
vector<int> v2{10};    // v2有1个元素10

vector<int> v3(10, 1);    // v3有10个元素,初始化为1
vector<int> v4{10, 1};    // v4有2个元素,初始值为10和1

如果我们使用的是圆括号,可以说提供的值是用来构造vector对象的,当我们使用花括号时,表示我们想列表初始化,但当我们使用了花括号,但提供的值不能用来列表初始化时,就要考虑用这样的值构造vector对象:

vector<string> v5{"hi"};    // 列表初始化,v5有一个元素
vector<string> v6("hi");    // 错误,不能用字符串字面值构建vector对象
vector<string> v7{10};    // v7有10个空串元素
vector<string> v8{10, "hi"};    // v8有10个"hi"元素

尽管以上例子中用了花括号,但除了v5之外都不是列表初始化,要想列表初始化vector对象,花括号里的值必须与元素类型相同。

向vector对象中添加元素:

vector<int> v2;
for (int i = 0; i != 100; ++i){
    v2.push_back(i);    // 依次把i放到v2尾端
}    //循环结束后v2中有100个元素,值从0到99

vector对象能高效增长,因此不必在定义时指定大小,如果这么做性能可能更差。

如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环。范围for循环语句体内不应改变其所遍历序列的大小。

其他vector操作:

操作作用
v.empty()如v中不含元素,返回true,否则返回false
v.size()返回v中元素个数,类型为vector<T>::size_type
v.push_back(t)向v的末尾添加一个值为t的元素
v[n]返回v中第n个位置元素的引用(从0开始)
v1 = v2用v2中的元素拷贝替换v1中的元素
v1 = {a, b, c}用列表中的元素拷贝替换掉v1中的元素
v1 == v2v1和v2相等当且仅当他们元素数量且对应位置元素值都相同
v1 !=(<,<=,>,>=) v2以字典顺序进行比较(只能比较能比的类型)

对于size()返回值:

vector<int>::size_type;    // 正确
vector::size_type;    // 错误

两个整数相除,小数部分被舍去。

vector只能用下标访问元素,不能用下标形式添加元素:

vector<int> ivec;
ivec[0] = 0;    // 错误

用下标访问vector时,如下标元素不存在,编译器不会发现这种错误,在运行时会产生一个不可预知的值,所谓的缓冲区溢出指的就是这类错误。

除了用下标访问string对象和vector对象,还有更通用的机制:迭代器,标准库还定义了几种容器,所有标准库容器都能用迭代器,而只有少数几种能用下标访问。string不属于容器,但支持迭代器。

迭代器提供对对象的间接访问,迭代器的对象是容器中的元素或string对象中的字符,用迭代器可以访问某元素,也可以从一个元素移动到另外一个元素。迭代器有有效和无效之分,有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置,其他所有情况都属于无效。

使用迭代器:

auto b = v.begin(), e = v.end();

b表示v的第一个元素;e表示v尾元素的下一位置,被称为尾后迭代器。如果容器为空,则begin和end返回的是同一个尾后迭代器。

迭代器运算符:

操作含义
*iter返回迭代器所指元素的引用
iter->mem解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem
++iter令iter指示容器中的下一个元素
–iter令iter指示容器中的上一个元素
iter1 == iter2判断两个迭代是否相等,如果两个迭代器指示的是同一位置的元素或者它们是同一个容器的尾后迭代器,则相等

通过解引用迭代器来获取它所指示的元素,执行解引用的迭代器必须合法并确实指向某元素,试图解引用一个非法迭代器或尾后迭代器都是未定义的行为:

// 将String对象的首字母大写
	string str("some string");
	string::iterator b = str.begin(), e = str.end();
	if (b != e){    // 检查是否是空串
		*b = toupper(*b);
	}

string的迭代器类型为string::iterator,*b获取了迭代器b所指向的字符的引用。

end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用。

迭代器类型:

vector<int>::iterator it;    // it能读写vector<int>的元素
string::iterator it2;    // it2能读写string对象中的字符

vector<int>::const_iterator it3;    // it3只能读元素,不能写元素
string::const_iterator it4;    // it4只能读字符,不能写字符

与常量指针类似,如果string或vector对象是一个常量,则只能使用const_iterator类型,若不是常量,则两种类型都能用。

begin和end运算符返回的具体类型由对象是否是常量决定,如果是常量,返回const_iterator,否则返回iterator:

vector<int> v;
const vector<int> cv;
auto it1 = v.begin();    // it1类型是vector<int>::iterator
auto it2 = cv.begin();    // it2类型是vector<int>::const_iterator

有时我们需要const_iterator类型但返回的并非常量类型,C++11新标准引入了两个新成员函数cbegin和cend,总是返回const_iterator类型:

auto it3 = v.cbegin();    // it3类型为vector<int>::const_iterator

解引用迭代器可以获得迭代器所指的对象,如果该对象是类类型,有可能想继续访问它的成员。如果是string组成的vector对象,要想检查其元素是否为空,令it是该vector对象的迭代器:

(*it).empty();    // 正确
*it.empty();    // 错误

注意以上代码中圆括号必不可少,上面第二个表达式含义是从名为it的对象中寻找empty成员,显然it是一个迭代器,没有该成员。

为简化上述表达式,C++语言定义了箭头运算符->,把解引用和成员访问两个操作结合在一起:

it->mem;
(*it).mem;    // 两式含义相同

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

// 将vector<string>中非空串前所有串大写
	vector<string> svec(10, "hi");
	svec.push_back("");
	svec.push_back("hi");
	vector<string>::iterator b = svec.begin(), e = svec.end();
	for ( ; b != e && !b->empty(); b++){
		for (int i = 0; i != b->size(); i++){
			(*b)[i] = toupper((*b)[i]);    // 不能写为*b[i]
		}
	}

迭代器的递增运算符令迭代器每次移动一个元素,所有标准库容器都支持递增运算符。==和!=也能对任意标准库类型的两个迭代器比较。

string和vector的迭代器提供了更多额外运算符,以下是vector和string(deque和array也支持,但其他容器都不支持)支持的运算:

操作含义
iter + n迭代器指示新位置比原来向尾方向移动了n个元素
iter - n迭代器指示的新位置比原来向头方向移动了n个元素
iter += n同第一行
iter -= n同第二行
iter1 - iter2结果是它们之间的距离。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一个位置
>、>=、<、<=更接近尾部的大。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一个位置

迭代器减法得到的是两个迭代器的距离,类型是difference_type的带符号整型数。

迭代器实现二分查找:

	vector<int> ivec;
	ivec.push_back(1);
	ivec.push_back(2);
	ivec.push_back(3);
	ivec.push_back(4);
	ivec.push_back(5);
	ivec.push_back(6);
	ivec.push_back(7);
	ivec.push_back(8);
	ivec.push_back(9);
	vector<int>::iterator b = ivec.begin(), e = ivec.end();
	vector<int>::iterator mid = b + (e - b) / 2;
	int sought = 10;
	while (b <= e && *mid != sought){
		if (sought < *mid){
			e = mid - 1;
		} else {
			b = mid + 1;
		}
		mid = b + (e - b) / 2;
	}
	if (*mid == sought){
		cout << "success" << mid - ivec.begin() << endl; 
	} else {
		cout << "can not find the num." << endl;
	}

迭代器实现分数段人数统计:

#include <iostream>
#include <vector>
using namespace std;

int main(){
	vector<int> ivec(11);
	int num;
	while (cin >> num){
		*(ivec.begin() + num / 10) += 1;
	}
	for (int i = 0; i != ivec.size(); i++){
		cout << ivec[i] << endl;
	}
	return 0;
}

数组的大小固定不变,因此对某些特殊的应用来说程序的运行时性能较好,但是相应地也损失了一些灵活性。

数组是一种复合类型,数组中元素个数也属于数组类型的一部分,编译的时候维度应该是已知的,即维度必须是一个常量表达式。

unsigned cnt = 42;    // 不是常量表达式
constexpr unsigned sz = 42;    // 常量表达式
int arr[10];    // 含10个整数的数组
int *parr[sz];    // 含42个整型指针的数组
string bad[cnt];    // 错误,cnt不是常量表达式
string strs[get_size()];    // 当get_size是constexpr时且返回值真正为常量表达式时正确

对于上述代码最后一行:

constexpr int get_size(const int i) {
	return i + 3;
}

int main() {
	int i = 4;
	const int j = 4;
	string strs[get_size(i)];    // 错误,因为get_size的参数不是常量表达式
	string strs1[get_size(j)];    // 正确,j为常量表达式
}

默认情况下,数组元素被默认初始化。和内置变量类型一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值,该未定义值可被访问。但如果是类类型数组,元素会被默认初始化为类构造函数构造的默认值。

定义数组时必须指定数组的类型,不允许用auto关键字由初始化的列表推断类型。和vector一样,数组元素应为对象,不存在引用的数组。

可以对数组元素进行列表初始化,此时允许忽略数组的维度,编译器会根据初始值的数量推断出来。如指定了维度,则初始值的总数量不应该超出指定的大小,如果维度比提供的初始值数量大,剩下元素被值初始化(类类型由类初始化,内置类型初始化为0)。

字符数组的特殊性:

char a1[] = {'C', '+', '+'};    // 列表初始化,没有空字符
char a2[] = {'C', '+', '+', '\0'};    // 列表初始化,含有显式空字符
char a3[] = "C++";    // 同上,多出了一个空字符
char a4[6] = "Daniel";    // 错误,实际含七个元素

不允许拷贝和赋值:

int a[] = {0,1,2};
int a2[] = a;    // 错误,不允许用一个数组初始化另一个
a2 = a;    // 错误,不允许把一个数组直接赋值给另一个数组

一些编译器支持数组赋值,这是编译器扩展,最好不要用。

复杂的数组声明:

int *ptrs[10];    // ptrs是含有10个整型指针的数组
int &ptrs[10];    // 错误,不存在存放引用的数组
int (*Parray)[10];    // 指向存有10个int型元素数组的指针
int (&arrRef)[10];    // 正确,引用一个含有10个int型元素的数组

默认情况下,类型修饰符从右向左依次绑定。对ptrs来说,首先是一个含有10个元素的数组,其次名字是ptrs,然后是int*,表示存放的是int型指针。但对Parray来说,最好从内向外读,首先*Parray表示一个指针,再看右边,说明指向的是含十个元素的数组,在看左边,说明元素类型为int *:

int *(&array)[10] = ptrs;    // 首先是一个引用,其次是引用含十个元素的数组,最后数组元素类型为int*

数组下标的类型是size_t,是一种机器相关的无符号类型,它被设计的足够大能表示内存中任意对象的大小,定义在cstddef头文件中,这个库是stddef.h的C++版本。

数组也可以使用范围for语句遍历:

int scores[11];
for (auto i : scores) {
    cout << i  << endl;
}

数组使用的下标运算符和vector使用的下标运算符不同,数组使用的是C++语言定义的,只能用在数组类型的运算对象上,vector使用的下标运算符是vector库自己定义的。

数组与vector和string一样,下标是否在合理范围之内由程序员检查。

使用数组的时候编译器一般会把它转换成指针。取地址符可以作用于任何对象,包括数组元素:

string nums[] = {"one", "two", "three"};
string *p = &nums[0];    // p指向nums的第一个元素
string *p2 = nums;    // 用到数组名字的地方,编译器会自动将其替换为一个指向数组首元素的指针,等价于上句代码

大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针:

int ia[] = {0,1,2,3,4,5,6,7,8,9};
auto ia2(ia);    // ia2是指向整型的指针,指向ia的第一个元素,等价于auto ia2(&ia[0])
ia2 = 42;    // 错误,使用int给int*赋值

但当使用decltype关键字时:

decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};    // 此时ia3类型为含10个整数的数组

指针也是迭代器:

int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *p = arr;    // p指向arr的第一个元素
++p;    // p指向arr[1]

我们可以获取到arr的首地址,获取arr的尾后指针可以这样:

int *e = &arr[10];    // 指向了不存在的元素,因此不能对其解引用或自增

用以上指针重新循环输出数组元素:

    int nums[10] = {0,1,2,3,4,5,6,7,8,9};
	int *b = nums, *e = &nums[10];
	for (; b != e; b++){
		cout << *b << endl;
	}

C++11新标准引入了begin和end两个函数,这两个函数定义在iterator头文件中,与容器中两个同名成员功能类似,但数组不是类类型,因此这两个函数不是成员函数:

int ia[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(ia);    // 返回ia首元素指针
int *last = end(ia);    // 返回ia尾元素下一位置指针

指向数组元素的指针所能做的操作与迭代器运算相同,包括解引用、递增、比较、与整数相加、两个指针相减等。

两个指针相减的结果是他们之间的距离,参与运算的两个指针必须指向同一数组中的元素:

int arr[5];
auto n = end(arr) - begin(arr);    // n的值是5,也是arr中元素的数量

两指针相减的类型为ptrdiff_t的标准库类型,和size_t一样,定义在cstddef头文件中的且机器相关,但ptrdiff_t是带符号类型。

两个指针比较同样适用于空指针和所指对象并非数组的指针,后一种情况下,两个指针必须指向同一个对象或该对象的下一个位置。如p是空指针,允许p加上或减去一个值为0的整型常量表达式。两个空指针也允许彼此相减,结果为0。

允许解引用和指针运算交互:

int ia[] = {0,1,2,3,4};
int last = *(ia + 4);    // last = ia[4]

标准库限定下标类型(如string和vector规定的)是无符号类型,而内置的下标运算无此规定:

int *p = &ia[2];
int head = *(p - 2);    // head值为ia[0]
int head2 = p[-2];    // 等价于上句

C风格字符串不是一种类型,而是一种约定俗成的表达和使用字符串的写法,按此习惯书写的字符串存放在字符数组中并以空字符结束。

C标准string函数是C语言标准库提供的一组函数,这些函数可用于操作C风格字符串,他们定义在cstring头文件中,这个头文件是string.h的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

以上函数不负责验证其字符串参数,传入此类函数的指针必须指向以空字符为结束的数组:

char ca[] = {'c', '+', '+'};
cout << strlen(ca) << endl;    // 错误,没有以空字符结束,结果未定义

比较字符串:

string s1 = "as";
string s2 = "ad";    // 显然s1 > s2
// 如用在C风格字符串上:
const char ca1[] = "as";
const char ca2[] = "ad";
if (ca1 < ca2);    // 比较的是两个地址,结果未定义
if (strcmp(ca1, ca2) > 0);    // true,ca1 > ca2

对大多数应用来说,string比C风格字符串更安全。

混用string对象和C风格字符串:任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代(允许使用以空字符结束的字符数组初始化string对象或对string对象赋值;在string对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象)。但反过来就不行了:

string s;
char *str = s;    // 错误,不能用string对象初始化char*
const char *str = s.c_str();    // 正确。string对象提供c_str函数将其转化为以空字符结尾的C风格字符串,结果指针类型为const char *

string.c_str函数返回的是const char*类型以保证我们不会改变字符数组的内容,如果我们改变了string的内容,*str指向的C风格字符串也会改变,事实上,如果我们后续操作改变了s的值就可能让之前返回的数组失去效用。如果执行完c_str函数后程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。

使用数组初始化vector对象:

int int_arr[] = {0,1,2,3,4,5};
vector<int> ivec(begin(int_arr), end(int_arr));    // ivec被初始化为int_arr[]中元素

使用以上方法初始化vector对象也可以仅是数组的一部分,指针指向区域前闭后开。

建议尽量用标准库类型而非数组。

严格来说,C++中没有多维数组,通常说的多维数组是数组的数组。对于二维数组来说,通常把第一个维度称为行,第二个维度称为列。

多维数组的初始化:

int ia[3][4] = {
    {0,1,2,3},
    {4,5,6,7},
    {8,9,10,11}
};
int ia[3][4] = {1,2,3,4,5,6,7,8,9,10,11};    // 同上
int ia[3][4] = {{0},{4},{8}};    // 仅初始化每行第一个元素

多维数组下标引用:

ia[2][3] = 2;
int (&row)[4] = ia[1];    // 把row绑定在ia的第二个4元素数组上

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

// 将数组元素值改为元素位置
    int i[3][4];
	size_t s = 0;
	for (auto& row : i) {
		for (auto& col : row) {
			col = s;
			s++;
		}
	}

以上代码中因为要修改其值,所以范围for循环中的定义为引用,但以下代码:

// 输出数组值
for (const auto &row : ia){
    for (const auto &col : row){
        cout << col << endl;
    }
}

以上代码范围for循环也要使用引用类型,因为如果不用引用:

for (auto row : ia)    // 程序无法通过编译,因为编译器自动把ia中的数组元素当做int型指针赋给row,之后再对row循环不合法。
    for (auto col : row);
// 相当于:
int b[4] = {3,2,1,0};
auto j = b;    // 数组名用作指向第一个元素的指针,j类型为int型指针
               // 而范围for语句中相当于把ia中的数组赋值给row,row类型自然为int型指针

因此对多维数组范围for循环时,除了最内层,其余都要使用引用类型。

程序使用多维数组名字时,也会自动转化为指向数组首元素的指针:

int ia[3][4];
int (*p)[4] = ia;    // p指向含有4个整数的数组ia[0]
p = &ia[2];    // p指向ia尾元素(ia[2])

随着C++11新标准的提出,通过auto或begin、end函数就能避免指向数组的指针这么复杂的写法了:

	int i[3][4];
	for (int(*p)[4] = i; p < i + 3; p++) {
		for (int *q = *p; q < *p + 4; q++) {
			cout << *q << endl;
		}
	}
// 与以下代码等价:
	for (auto p = i; p != i + 3; ++p) {
		for (auto q = *p; q != *p + 4; ++q) {
			cout << *q << endl;
		}
	}
// 与以下代码等价:
	for (auto p = begin(i); p != end(i); p++) {
		for (auto q = begin(*p); q != end(*p); q++) {
			cout << *q << endl;
		}
	}

使用类型别名可以简化读、写和理解一个指向多维数组的指针:

using int_array = int[4];    // C++11新标准
typedef int int_array[4];    // 旧标准与上句等价

int main() {
	int i[3][4] = { 0 };
	for (int_array* p = i; p < i + 3; p++) {    // p类型为int (*p)[4],因为i当做数组名使用时代表指向第一个元素的指针,i的元素类型是int[4]
		for (int* q = *p; q < *p + 4; q++) {    // q的类型为int*,因为p是指向int型数组的指针,解引用后代表数组,当做数组使用时代表指向第一个元素的指针,元素类型为int
			cout << *q << endl;
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值