C/C++教程

《C++ Primer》【Chapter 3】

本文主要是介绍《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;
}
这篇关于《C++ Primer》【Chapter 3】的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!