C++学习笔记(二)

《C++ Primer》第三章 - 字符串、向量和数组

1. 命名空间和 using 声明

1.1 命名空间的 using 声明
  • std::cin 编译器应从标准输入 std 中寻找名字cin
1.2 using 声明
  • using namespace::name;

  • 位于头文件的代码一般来说不应该使用 using 声明

2. 标准库类型 string

2.1 定义和初始化 string 对象
#include <string>
using std::string;

string s1;						// 默认初始化,s1 是一个空串
string s2(s1);					// s2 是 s1 的副本,direct initialization
string s2 = s1;					// 等价于上一条语句,copy initialization
string s3("value");				// s3 是字面值 “value” 的副本,除了字面值最后的哪个空字符外
string s3 = "value";			// 等价于上一条语句
string s4(n, 'c');				// 把 s4 初始化为连续 n 个字符 ’c' 组成的串,direct initialization
string s5 = string(10, 'c')		// copy initialization,可读性较差
2.2 string 对象上的操作
  • 读写 string 对象

    #include <string>
    using std::cin; std::cout; std::endl;
    using std::string; 
    
    int main() {
        string s;
        cin >> s;				// 将 string 对象读入 s,遇到空白停止,忽略开头空白(空格、回车、制表符等)
        cout << s << endl;
        return 0;
    }
    
  • 读取未知数量的 string 对象

    #include <string>
    using std::cin; std::cout; std::endl;
    using std::string;
    
    int main() {
        string word;
        while (cin >> word)				// 反复堆区,直至流无效,遇到文件结束标记或者非法输入后循环结束
            cout << word << endl;		// 诸葛输出单词,每个单词后面紧跟一个换行
        return 0;
    }
    
  • getline 从输入流中读入内容,直到遇到换行符为止(换行符也被读进),然后把所读的内容存入到 string 对象中去(不包括换行符)。getline 只要一遇到换行符就结束读取操作并返回结果,因此最输入换行符的话会返回空的 string

    int main() {
        string line;
        while (getline(cin, line))		// 从 cin 中读入内容,直到遇到换行符为止(换行符也被读进)
            cout << line;				// 每行过后加上换行符并刷新显示缓存区
        return 0;
    }
    
  • emptysize

    // 改写上个语句块中的对应内容,只输出非空行
    while (getline(cin, line))
    	if (!line.empty())				// empty 函数根据 string 对象是否为空返回布尔值,空 则返回 真
            cout << line << endl;
    
    // 改写上个语句块中的对应内容,只输出长度超过80的行
    while (geline(cin, line))
        if (line.size() > 80)
            cout << line << endl;
    
    • size 函数返回值的类型是 string::size_type 类型,是一个无符号的整型数,因此表达式中已经有了 size() 函数时就不要再使用 int ,以避免混用 intunsigned 可能带来的问题
  • 比较 string 对象

    • 对大小写敏感;
    • 如果两个 string 对象的长度不同,而且较短的string 对象的每个字符都与较长 string 对象对应位置上的字符相同,则较短的 string 对象 小于 较长的 string 对象;
    • 如果两个 string 对象爱某些对应的位置上不一致,则 string 对象比较的结果其实是 string 对象中第一对相异字符比较的结果(按字典排序);
  • string 对象相加:每个加法运算符的两侧的运算对象至少有一个是 string 对象,字面值字符串也 string 是不同的类型

string s1 = "hello", s2 = "world";
string s3 = s1 + "," + s2 + '\n';			// 正确:将一个 string 对象和一个字面值相加
string s4 = ”hello“ + "," + s2;				// 错误:不能把字面值直接相加
  • 处理 string 对象中的字符

    • cctype 头文件中的函数

      函数名c 为何种类型时为真 / 函数功能
      isalnum(c)字母或者数字
      isalpha(c)字母
      iscntrl(c)控制符
      isdigit(c)数字
      isgraph(c)不是空格但是可打印
      iflower(c)小写字母
      isprint(c)可打印字符(空格或者具有可视形式)
      ispunct(c)标点符号(不是上面的各种类型)
      isspace(c)空白(控制、横向制表符、纵向制表符、回车符、换行符、进纸符)
      isupper(c)大写字母
      isxdigit(c)十六进制数字
      tolower(c)将 c 变为或保持为小写字母
      toupper(c)将 c 变为或保持为大写字母
    • 处理每个字符

      string str("some string");
      for (auto c : str)					// 赋值,字符串 str 不改变
      	cout << c << endl;
      
      for (auto &c : str)
          c = toupper(c);					// c 是一个引用,字符串 str 被改变
      cout << c << endl;
      
    • 处理部分字符

      string str("some string");
      if (!str.empty())
      	str[0] = toupper(str[0]);		// 下标运算符 [] 返回字符串在索引值相应位置的字符的引用,原字符串改变
      

3. 标准库类型 vector

  • vector 表示对象的集合,其中所有对象的类型都相同,常被称作 容器 container
  • 不能在范围 for 循环中向 vector 对象添加元素。
3.1 定义和初始化 vector 对象
#include <vector>
using std::vector;

vector<int> ivec;					// 初始状态为空
vector<int> ivec2(ivec);			// 把 ivec 的元素拷给 ivec2
vector<int> ievc3 = ievc;			// 把 ivec 的元素拷给 ivec3
vector<string> sevc(ivec2);			// 错误:把 ivec 的元素是 string 对象,不是 int
3.2 列表初始化
vector<string> v1{"a", "b", "c"};		// 列表初始化
vector<string> v2("a", "b", "c");		// 错误

// () 表示构建 vector 对象
vector<int> v3(10);						// v3 中有10个元素,每个的值都是0
vector<int> v4{10};						// v4中有1个元素,其值为10
vector<int> v5(10, 1);					// v5 中有10个元素,每个的值都是1
vector<int> v6{10, 1};					// v6 中有两个元素,分别是10和1

vector<string> v7("hi");				// 错误:不能用自负床字面值构建 vector 对象
vector<string> v8{10};					// v8 中有10个默认初始化的元素
vector<string> v9{10, "hi"};			// v9 中有10个值为 ”hi“ 的元素
3.3 向 vector 对象中添加元素:push_back
vector<int> v2;
for (int i = 0; i != 100; ++i)
	v2.push_back(i);

string word;
vector<string> text;
while(cin >> word)
    text.push_back(word);

vector<int>::size_type					// 正确
vector::size_type						// 错误,需要指定类型
3.4 索引
// 以10分为一个分数段统计成绩的数量:0~9,10~19,…,90~99,100
vector<unsigned> scores(11, 0);			// 11个分数段,初始化为0
unsigned grade;
while (cin >> grade) {
    if (grade <= 100)
        ++scores[grade/10];
}

4. 迭代器

4.1 beginend
auto b = v.begin(), e = v.end();		// b 和 e 类型相同
  • begin 负责返回指向第一个元素的迭代器。
  • end 尾后迭代器(off-the-end iterator),负责返回指向容器(或 string)”为元素的下一位置(one past the end)“的迭代器。
  • 如果容器为空,则 beginend 返回的是同一个迭代器,都是尾后迭代器。
  • beginend 返回的具体类型有对象是否是常量决定,如果对象是常量,返回 const_interator;否则,返回 iterator
  • cbegincend 固定返回const_interator
4.2 迭代器运算符
符号作用
*iter返回迭代器 iter 所指元素的引用
iter->men解引用 iter 并获取该元素的名为mem 的成员,等价于 (*iter).mem
++iteriter 指示容器中的下一个元素
--iteriter 指示容器中的上一个元素
iter1 == iter2判断两个迭代器是否相等,如果两个迭代器知识的是同一个元素
iter1 != iter2或者它们是同一个容器的尾后迭代器,则相等;反之,不相等
  • 不能对 end 返回的迭代器进行递增或者解引用的操作
4.3 迭代器类型
  • 拥有迭代器的标准库类型使用 iteratorconst_interator 来表示迭代器的类型

    vector<int>::interator it;				// it 能读写 vector<int> 元素
    string::iterator it2;					// it2 能读写 string 对象中的字符
    
    vector<int>::const_iterator it3;		// it3 只能读元素,不能写元素
    string::const_interator it4;			// it4 只能读字符,不能写字符
    
4.4 结合解引用和成员访问操作
(*it).empty()					// 解引用 it,然后调用结果对象的 empty 成员
*it.empty()						// 错误:试图访问 it 的名为 empty 成员

// 依次输出 text 的每一行直至遇到第一个空白行为止
for (auto it = text.cbegin(); it != text.cend() && !it->empty(); ++it)
	cout << *it << endl;
4.5 迭代器使用的注意事项
  • 但凡是使用了迭代器的循环体,都不要想迭代器所属的容器添加元素;
  • 不要解引用非法迭代器或者尾后迭代器;
4.6 迭代器运算
auto mid = vi.begin() + vi.size() / 2;		// 计算得到最接近 vi 中间元素的一个迭代器
if (it < mid)
	// 处理 vi 的前半部分元素
// 使用迭代器实现有序序列的二分搜素,搜索目标是 sought
// text 必须是有序的
auto beg = text.begin(), end = text.end();
auto mid = beg + (end - beg) / 2;
while (*mid != sought) {
    if (*mid < sought) beg = mid;
    if (*mid > sought) end = mid;
    mid = beg + (end - beg) / 2;
}
  • 参与比较的两个迭代器必须合法并且指向的是同一个容器的元素;
  • 两个迭代器相减得到的结果是两个迭代器的距离,其类型时名为 difference_type 的带符号整型数;

5. 数组

5.1 定义和初始化内置数组
unsigned cnt = 42;						// 不是常量表达式
constexpr unsigned sz = 3;				// 常量表达式
int arr[10];							// 含有10个整数的数组
int *parr[sz];							// 含有3个整数型指针的数组
string bad[cnt];						// 错误:cnt 不是常量表达式

int a1[] = {0, 1, 3};					// 维度是3的数组
int a2[sz] = {0, 1};					// 维度是3的数组,{0, 1, 0}
int a3[sz] = {0, 1, 2, 3};				// 错误:初始值过多

// 字符数组的特殊性
char a4[] = {'C', '+', '+'};			// 列表初始化,没有空字符
char a5[] = {'C', '+', '+', '\0'};		// 列表初始化,含有显式的空字符
char a6[] = "C++";						// 自动添加表示字符串结束的空字符
const char a4[4] = "Ulic";				// 错误:没有空间可以添加空字符

char a7[] = a6;							// 错误:不允许使用一个数组初始化另一个数组
a8 = a6;								// 错误:不能把一个数组直接赋值给另一个数组

// 一些比较复杂的数组声明
int *ptrs[10];							// ptrs 是含有10个整型指针的数组
int &refs[10] = /* ? */;				// 错误:不存在引用的数组
int (*Parray)[10] = &arr;				// Parray 是一个指针,指向维度为10的整型数组
int (arrRef)[10] = arr;					// arrRef 是一个引用,引用的是维度为10的整型数组
int *(&arry)[10] = ptrs;				// arry 是一个引用,引用的对象是维度为10的数组,数组的元素类型是整型指针
  • 定义数组的时候必须指定数组的类型,不允许使用 auto 关键字有初始值的列表推断类型;
  • vector 一样,数组的元素应为对象,因此不存在引用的数组;
  • 数组不允许拷贝和赋值;
  • 理解数组声明的含义时最好从数组的名字开始由内向外阅读;
  • 数组元素的个数的值类型为 size_t
5.2 访问数组元素
// 以10分为一个分数段统计成绩的数量:0~9,10~19,…,90~99,100
unsigned scores[11] = {};			// 11个分数段,初始化为0
unsigned grade;
while (cin >> grade) {
    if (grade <= 100)
        ++scores[grade/10];
}
// 输出 scores 中的每个元素
for (auto i : scores)
    cout << i << " ";
cout << endl;
5.3 数组和指针
int arr[] = {0, 1, 2, 3, 4, 5};
auto a1(arr);								// a1 是一个指针
auto a1(&arr[0]);							// 与上一条命令等价
decltype(arr) a2 = {0, 1, 2, 3, 4, 5};		// a2 是数组,而不是指针

// 用指针遍历数组中的元素
int *e = &arr[6];							// 指向数组 arr 尾元素的下一位置
for (int *p = arr; p != e; ++p)
    cout << *p << endl;
  • 在大多数表达式中,使用数组类型的对象其实时使用一个只想该数组首元素的指针;
  • 当使用 decltype 关键字时,编译器不会将数组名字替换为指向数组首元素的指针;
  • 两个指向同一数组元素的指针相减得到的结果是他们之间的距离,其类型是名为ptrdiff_t 的标准库类型,定义在 cstddef 头文件中;
5.4 标准库函数 beginend
#include <iterator>

int arr[] = {0, 1, 2, 3, 4, 5};
int *beg = begin(arr);						// 指向 arr 首元素的指针
int *last = end(arr);						// 指向 arr 尾后元素的指针
5.5 指针和下标
int arr[] = {0, 1, 2, 3, 4, 5};
int *p = &arr[2];
int a = p[1];								// 等价于 *(p + 1),也就是 arr[3]
int b = p[-2];								// 等价于 *(p - 2),也就是 arr[0]
  • 内置的下标运算符所用的索引值不是无符号类型,这一点与 vectorstring 不一样;
5.6 多维数组
  • 多维数组的初始化

    int a[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};	// 等价于
    int a[3][4] = {
    	{0, 1, 2, 3},
    	{4, 5, 6, 7},
    	{8, 9, 10, 11}
    }
    int ia[3][4] = {{0}, {4}, {8}};							// 初始化每一行的第一个元素
    
  • 多维数组的下标引用

    // 创建二维数组,以每个元素的索引值作为其元素值
    constexpr size_t rowCnt = 3, colCnt = 4;
    int arr[rowCnt][colCnt];
    for (size_t i = 0; i != rowCnt; ++i) {				// 对于每一行
        for (size_t j = 0; j != colCnt; ++j) {			// 对于每一列
            arr[i][j] = i * colCnt + j;
        }
    }
    
  • 使用范围 for 语句处理多维数组

    // 功能同上一个代码块
    constexpr size_t rowCnt = 3, colCnt = 4;
    int ia[rowCnt][colCnt];
    size_t cnt = 0;
    for (auto &row : ia)					// 对于外层数组的每一个元素
        for (auto &col : row) {				// 对于内层数组的每一个元素
            col = cnt;
            ++cnt;
        }
    
    • ia 是一个维度为 3 的数组 的首个元素,这个元素是一个维度为 4 的数组;

    • 循环中,row 分别是对 ia[0]ia[1]ia[2] 的引用,ia[0]ia[1]ia[2] 均是是维度为 4 的数组;

    • col 分别是对 ia[0]ia[1]ia[2] 这三者中各个元素的引用;

    • 要使用范围 for 语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型;

      for (const auto &row : ia)
          for (auto col : row)
              cout << col << endl;		// 正确
      
      for (auto row : ia)
          for (auto col : row)			// 错误:编译器会自动将数组形式的元素转换成指针,
              /* ? */						// 因此 row 的类型是 int*,无法遍历
      
  • 指针和多维数组

    int arr[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
    for (auto p = begin(arr); p != end(arr); ++p) {
        for (auto q = begin(*p); q != end(*p); ++q)
            cout << *q << " ";
        cout << endl;
    }
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值