《C++ Primer》【Chapter 3】
作者:互联网
chapter3 字符串、向量和数组
using
using 有一个更细的用法就是直接指明命名空间中的名字
//using namespace::name;
using std::cin;
一个要注意的点是:头文件中不应包含using声明
因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件都会有这个声明,这样可能会在不经意间产生命名冲突。
string-可变长的字符序列
定义和初始化除了常用的以外,还可以通过指定数目生成连续的字符串。
string s(10, 'c');
拷贝初始化和直接初始化
string s1;
string s2(s1);
string s2 = s1;
string s3("value");
string s3 = "value";
string s4(n, 'c');
string s5 = "hiya"; //使用等号一般都是拷贝
string s6("hello"); //直接
string s7(10, 'c'); //直接
string s8 = string(10, 'c'); //拷贝
操作
操作 | 作用 |
---|---|
os<<s | 将s写到输出流os中,返回os, eg:cout<<s |
is>>s | 从is中读取字符串赋给s,字符串以空白分隔,返回is, eg:cin>>s |
getline(is, s) | 从is中读取字符串赋给s,字符串以空白分隔,返回is, eg:getline(cin, s) |
s.empty() | 判空 |
s.size() | 返回s中字符的个数 |
s[n] | 取第n个字符的引用,n从0算起 |
s1+s2 | 字符串拼接 |
s1=s2 | 用s2的副本拷贝给s1 |
s1!=s2 | 比较两个串,逐字符比较,对大小写敏感 |
<,<=,=>,> | 根据字典序比较,对大小写敏感 |
对于cin而言,读取字符串给string时,会忽略掉开头的空白,字符串的读入结束也是空白,当需要读入空白时,则需要使用getline, getline遇到换行结束
一些字符函数
操作 | 作用 |
---|---|
isalnum(c) | 当c是字母或数组时为真 |
isalpha(c) | 当c是字母时为真 |
iscntrl(c) | 当c是控制字符时为真 |
isdigit(c) | 当c是数字时为真 |
isgraph(c) | 当c不是空格但可以打印时为真 |
islower(c) | 当c是小写时为真 |
isprint(c) | 当c是可打印字符时为真(即c为空格,或具有可视形式) |
ispunct(c) | 当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种) |
isspace(c) | 当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种) |
isupper(c) | 当c是大写字符时为真 |
isxdigit(c) | 当c是十六进制数字时为真,更直白的理解是不是(01,af,A~F)中的字符 |
tolower(c) | 如果c是大写字母,返回小写;否则原样 返回 |
toupper(c) | 如果c是小写字母,返回大写;否则原样 返回 |
string::size_type
配套类型size_type体现了标准库类型与机器无关的特性,在具体使用的时候,通过作用域操作符来表明名字size_type是在类string中定义的。即string::size_type。它肯定是无符号
的类型,所以尽量不要用int去定义s.size(),这样可能会带来问题
string s = "ddd"
unsigned len = s.size();
string s = "dwdadwwadaw";
for(string::size_type i = 0; i < s.size(); i++) {
s[i] = toupper(s[i]);
}
cout << s << endl;
string的+操作必须保证字符串字面值两边至少有一个string
原因:为了与C兼容,C++中string和字符串字面值是不同的类型!
vector
定义和初始化
用等号去赋值vector时,是拷贝,不是引用操作!
vector<int> vec = {0,1,2,3,4}; //列表初始化方法
vector<int> b = vec; //拷贝
vec[4] = 111; //b[4]并没有改变
当使用花括号时,会有不同的情况,可能是列表初始值,也可能是元素数量
使用vector指定元素数目初始化时要满足以下两个条件:
- 类必须要有明确的初始值或者有默认初始化函数
- 只提供了元素的数量而没有设定初始值,只能使用直接初始化
除非是所有元素值一样,那么定义一个空的vector然后逐个加入会比一开始指定vector的大小后添加可能更快
vector<int> v1(10); //10个元素,都是0
vetcor<int> v2{10}; //1个元素,10
vector<int> v3(10, 1); //10个元素,都是1
vectopr<int> v4{10, 1}; //2个元素,10、1
vector<string> v5{"hi"}; //列表初始化 1个元素
vector<string> v6("hi"); //错误! 不能用字符串字面值构建vector对象
vector<string> v7{10}; //10个默认初始化的元素
vector<string> v8{10, "hi"}; //10个值为"hi"的元素
vector对象不能通过下标符添加元素,下标符只能访问已存在的元素
由于编译器并不会检测出下标越界的情况,这可能会导致严重的缓冲区溢出(buffer overflow)错误。
迭代器
迭代器类型
- iterator:可以修改
- const_iterator:常量,且必须保证容器也是常量
为了便于专门的到const_iterator类型,C++11专门引入了两个函数cbegin()和cend()
const vector<int> cv;
auto itr = cv.begin(); //vector<int>::const_iterator
解引用迭代器
解引用时必须加括号,因为不加括号相当于时访问it的成员,而it只是迭代器类型
(*it).empty(); //正确
*it.empty(); //错误
箭头运算符->
箭头运算符把解引用和成员访问两个操作结合在一起,也就是说it->mem和(*it).mem表达的意思相同
注意
当使用迭代器时,如果容器如vector动态增长了(push_back),会使迭代器失效。
迭代器运算
iter1 - iter2
两个迭代器的相减结果是他们之间的距离。
数组
数组的声明中,维度必须是常量表达式。
unsigned cnt = 42;
int a[cnt]; // 错误
constexpr unsigned sz = 42;
int a[sz]; //正确
字符数组的特殊性
char a1[] = {'C', '+', '+'}; //列表初始化,没有空字符
char a2[] = {'C', '+', '+', '\0'}; //列表初始化,含有显式的空字符
char a3[] = "C++"; //自动添加表示字符串结束的空字符
const char a4[6] = "Daniel"; //错误,没有空间存放结束符
数组不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值。
int a[] = {0, 1, 2}
int a2[] = a; //错误,不允许使用一个数组初始化另外一个数组
a2 = a; //错误,不能把一个数组直接赋值给另一个数组
复杂的数组声明
就复杂数组而言,由内向外阅读,即先理解定义的名字是引用还是指针,然后在看外面是身累了类型的数组。
int *ptrs[10]; //ptrs是一个包含10个整型指针的数组
int &refs[10] = ?; //错误,不存在引用类型的数组
int (*Parray)[10] = &arr; //Parray是10个整型数组的指针
int (&arrRef)[10] = arr; //arrRef是10个整型数组的引用
int *(&arry)[10] = ptrs; //10个整型指针的数组的引用
访问数组元素
数组的下标是size_t
类型,在头文件cstddef
头文件中
指针和数组
指针也是迭代器
C++11中有新特性 可以使用begin和end直接获取数组的头指针和尾后指针
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *beg = begin(ia);
int *last = end(ia);
ptrdiff_t len = last - beg;
两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型,和size_t一样,ptrdiff_t也是一种定义在cstddef头文件中的机器相关的类型。因为差值可能为负值,所以ptrdiff_t为有符号类型。
当两个指针指向同一个数组
的元素,或指向该数组的尾元素的下一位置,就能利用关系运算符对其进行比较。
int *b = arr, *e = arr + sz;
while(b < e) {
b++;
}
下标和指针
int ia[] = {0, 2, 4, 5, 6, 7};
int *p = ia;
int i = *(p + 2) //等价于 i = p[2]
int *p = ia[2]; //这是错误的,出了ia是指针,其他的都要用&
int k = p[-2]; //不会报错
标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求,即可为负值,但是并不像python中一样有实际意义
C风格字符串
C风格的字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。
char ca[] = {'C', '+', '+'};
cout << strlen(ca); //严重错误,ca没有以'\0'空字符结束
为什么要减少使用C风格字符串,而推荐使用string
当使用C风格字符串时,非常容易出现安全问题,例如strcat函数,如果连接到前一个字符串大小不足以容纳拼接后到字符串,会导致严重的安全泄漏。
const char s1[] = "A string";
const char s2[] = "A different string";
int k = strcmp(s1, s2); //比较字符串函数, s1 = s2:k=0, s1 < s2:k<0, s1 > s2:k>0
char largeStr[100];
strcpy(largeStr, s1); //s1拷贝给largeStr
strcat(largeStr, s2); //将s2连接到largeStr后
与旧代码的接口
char数组和string
若要混用string和C风格字符串,需要使用c_str()函数。
需要注意的是,char指针可以初始化string,但是string不能初始化char指针
const char *str = s.c_str();
c_str函数返回的是const char*类型的,确保不会改变字符数组的内容,但我们无法保证c_str函数一直有效,如果后续操作改变了s的值就可能让之前返回的数组失去效用。如果需要一直使用或者想改变,最好拷贝一份。
数组初始化和vector对象
不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用vector对象初始化数组。相反的,允许使用数组来初始化vector对象。只需要指出想要初始化数组的初始位置和尾后位置指针就可以了。
int in_arr[] = {0, 1, 2, 3, 4, 5};
vector<int> ivec(begin(in_arr), end(in_arr));
多维数组
C++中没有多维数组,只有数组的数组
多维数组的初始化
int ia[3][4] = {0}; //数组所有元素初始化为0
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};
//将row定义成一个含有4个整数的数组的引用,然后将其绑定到ia到第二行
int (&row)[4] = ia[1];
使用for语句处理多维数组
外层循环使用引用类型的原因是:
- 可以改变数组元素的值
- 为了避免数组被自动转化成指针(因为ia是数组的数组,即第一维数组其实是指向其他维数组的指针数组),这样加了引用后,就直接就变成了可以遍历的数组
//true
size_t cnt = 0;
for(auto &row : ia) {
for(auto &col : row) {
col = cnt++;
}
}
//true
for(const auto &row : ia) {
for(auto col : row) {
cout << col << endl;
}
}
//false
for(auto row : ia) {
for(auto col : row) {
cout << col << endl;
}
}
上面代码最后一个错误的原因是:第一层循环其实是要取长度为n的数组。因为row不是引用类型,所以编译器初始化row时会自动将这些数组形式的元素转化成指向该数组内首元素的指针,这样row就是int*类型,那么第二层循环就不合法了,因为不能用auto去遍历int*类型。
要使用for去处理多维数组,除了最内层的循环外,其他所有循环的控制变量都要加引用类型
指针和多维数组
//指针声明中,圆括号必不可少
int ia[3][4];
int (*p)[4] = ia; //p指向含有4个整数的数组
p = ia[2];
//auto遍历
for(auto p = ia; p != ia + 3; ++p) {
//这里的p其实是指向ia[0/1/2]数组的指针,*p才是数组
for(auto q = *p; q != *p + 4; ++q) {
cout << *q << endl;
}
cout << endl;
}
//使用begin, end函数
for(auto p = begin(ia); p != end(ia); ++p) {
//这里的p其实是指向ia[0/1/2]数组的指针,*p才是数组
for(auto q = begin(*p); q != end(*p); ++q) {
cout << *q << endl;
}
cout << endl;
}
标签:Chapter,10,string,初始化,int,C++,数组,ia,Primer 来源: https://www.cnblogs.com/dybala21/p/16074037.html