当前位置: 首页 > news >正文

C++ Primer 学习 -- Day 2

第 3 章知识点总结

    • 3.1 .1 命名空间的using 声明
      • ==提醒==
    • 3.2.1 定义和初始化 string 对象
      • 直接初始化和拷贝初始化
    • 3.2.2 string 对象上的操作
      • ==提醒==
      • 比较 string 对象( vector 也一样 )
      • 字面值和 string 对象相加
      • ==提醒==
    • 3.2.3处理 string 对象中的字符
      • ==建议==
      • 处理每个字符?使用基于范围的 for 语句
      • 使用范围 for 改变字符串中的字符
      • 只处理一部分字符?
      • 题目
    • 3.3.0 标准库类型 vector
      • ==注意==
    • 3.4.0 迭代器
      • ==建议==
      • 解引用和成员访问操作
      • ==注意==
    • 3.5.1 定义和初始化数组
      • ==特点==
      • 理解复杂的数组声明
    • 3.5.3 指针和数组
      • ==数组特性==
      • 指针也是迭代器
      • 指针运算
      • 解引用和指针运算的交互
      • 下标和指针
    • 3.6.0 多维数组
      • 使用范围 for 语句处理多维数组
      • 指针和多维数组
      • ==题目==

3.1 .1 命名空间的using 声明

提醒

头文件不应包含 using 声明,因为头文件的内容会拷贝到所有引用它的文件夹中,如果头文件里有某个 using 声明,那么每个使用了该头文件的文件都会有这个声明。对于某些程序来说,由于不经意间包含了一些名字,反而可能产生始料未及的名字冲突。

3.2.1 定义和初始化 string 对象

string s1                                         // 默认初始化,s1是空串
string s2(s1)                                     // s2 是 s1 的副本
string s3("value")                                // s3 是字面值“value"的副本,除了字面值最后的那个空字符外
string s3 = "value"                               // 等价于 s3("value")
string s4(n, 'c')                                 // 把 s4 初始化为由连续 n 个字符c组成的串

直接初始化和拷贝初始化

​ 如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化(copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化(direct initialization)

当初始值只有一个时,使用直接初始化或拷贝初始化都可以。如果初始化要用到的值有多个,一般来说只能使用直接初始化的发生

string s3 = "value";                              // 拷贝初始化
string s4(n, 'c');                                // 直接初始化,把 s4 初始化为由连续 n 个字符c组成的串
string s8 = string(10, 'c');                      // 拷贝初始化// 等价于
string temp(10, 'c');
string s8 = temp;

3.2.2 string 对象上的操作

提醒

由于 size 函数返回的是一个无符号整型数,因此切记,如果在表达式中混用了带符号数和无符号数可能产生意想不到的结果。例如,假设 n 是一个具有负值的 int,则表达式 s.size() < n 的判断结果几乎是 true。这是因为负值 n 会自动转化成一个比较大的无符号值。

因此如果一个表达式中已经有了 size() 函数就不要再使用 int 了,这样可以避免混用 int 和 unsigned 可能带来的问题。

比较 string 对象( vector 也一样 )

​ ① 如果两个 string 对象的长度不同,而且较短 string 对象的每个字符都与较长 string 对象对应位置上的字符相同,就说较短 string 对象小于较长 string 对象。

​ ② 如果两个 string 对象在某些对应的位置上不一致,则 string 对象比较的结果其实是 string 对象中第一队相异字符比较的结果。

string str = "Hello";
string phrase = "Hello World";
string slang = "Hiya";// 比较结果
slang > phrase > str

字面值和 string 对象相加

当把 string 对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+) 的两侧的运算对象至少有一个是 string 对象

string s1 = "hello", s2 = "world";
string s4 = s1 + ", " ;                           // 正确
string s5 = "hello" + ", ";                       // 错误:两个都不是 string 对象
string s6 = s1 + ", " + "world";                  // 正确:每个加法运算符都有一个运算对象是 string
string s7 = "hello" + ", " + s2;                  // 错误:不能把字面值直接相加// string 加法的工作机理和连续输入连续输出是一样的(即从左到右),所以:
s6 = (s1 + ", ") + "world";                       // 括号里可以相加,加完仍为 string对象,可继续加
s7 = ("hello" + ", ") + s2;                       // 括号内加不了

提醒

C++ 语言中的字符串字面值并不是标准库类型 string 的对象。切记,字符串字面值与string 是不同的类型

3.2.3处理 string 对象中的字符

建议

使用 C++ 版本的 C 标准库头文件,C 语言的标准库形如 name.h ,C++ 则将这些文件命为 cname。也就是去掉了 .h 的后缀,而在文件名之前添加了字母 c。使用使用 C++ 版本的 C 标准库头文件可统一。

处理每个字符?使用基于范围的 for 语句

如果相对 string 对象中每个字符做点儿什么,目前最好的办法是使用 范围 for(range for)语句。 这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作。

// 范围 for 语句语法形式
for (declaration : expression) {statement
}
// 其中,expression 部分是一个对象,用于表示一个序列。declaration 部分负责定义一个变量,该变量将被用于访问序列中的基础元素。每次迭代,declaration 部分的变量会被初始化为 expression 部分的下一个元素值。// eg 逐行输出 string 对象中的字符
string str("some string");
for (auto c: str){cout << c <<endl;
}

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

如果想要改变 string 对象中字符的值,必须把循环变量定义成引用类型。记住,所谓引用只是给定对象的一个别名,因此当使用引用作为循环控制变量时,这个变量实际上被依次绑定到了序列的每个元素上。使用这个引用,我们就能改变它绑定的字符。

// 将字符串改写为大写
string s("Hello World!!!");
for (auto &c : s) {c = toupper(c);                                // c 是一个引用,因此赋值语句将改变 s 中字符的值
}
cout << s <<endl;

只处理一部分字符?

​ ① 使用下标

​ ② 使用迭代器

题目

// 以下程序合法,输出 \0;
string s;
cout << s[0] <<endl;

3.3.0 标准库类型 vector

​ C++ 语言既有类模板(class template),也有函数模板,其中 vector 是一个类模板。

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

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

vector<int> ivec;
vector<vector<string>> file;

注意

vector 对象可以动态的增长,但是有一些副作用:

① 不能在范围 for 循环中向 vector 对象添加元素。

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

3.4.0 迭代器

建议

如果对象只需读操作而无须写操作的话最好使用常量类型(比如const_iterator)。为了便于专门得到const_iterator类型的返回值,可用 cbegin() 和 cend() 函数

auto it3 = v.cbegin();

解引用和成员访问操作

// 检查对象是否为空?
(*it).empty()                                     // 正确,解引用it,再调用empty()函数判断
*it.empty()                                       // 错误:会先试图访问 it 的 empty 的成员,再解引用。而                                                        it 是迭代器没有empty 成员// 为了简化上述表达式,可以用 箭头运算符(->)。箭头运算符把解引用和成员访问两个操作结合在一起:
it->empty() == (*it).empty() 

注意

​ 在迭代器的算术运算中,要保证迭代器一直有效(不能小于begin 也不能大于 end,可以等于end)。如用迭代器进行二分法处理时有:

// 获取中间值,beg 、end 和 min 都是迭代器
mid = beg + (end - beg) / 2;
// 而不能用 直接相加除以2,这样迭代器相加不成立,指针加指针无意义:
min = (beg + end) / 2;                           // 错误

3.5.1 定义和初始化数组

特点

​ ① 全局数组,未初始化时,默认值都是 0;(多维数组一样)

​ ② 局部数组,未初始化时,默认值为随机的不确定的值;

​ ③ 局部数组,初始化一部分时,未初始化的部分默认值为 0;

​ ④ 定义数组时必须指定数组的类型,不允许用 auto 关键字由初始值的列表推断类型;

​ ⑤ 和 vector 一样,数组的元素应为对象,不存在引用的数组;

​ ⑥ 当用字符串字面值初始化数组时,一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其他字符一样被拷贝到字符数组中去;

​ ⑦ 不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值(vector可以)

const char a4[6] = "Danile";                     // 错误:没有空间可以存放空字符

理解复杂的数组声明

// 读取方法:从(括号)里到外,从右往左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 个指针

3.5.3 指针和数组

数组特性

在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针。

string nums[] = {"one","two","three"};
string *p2 = nums;                               // 等价于 *p2 = &nums[0]

在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。所以当使用数组作为一个 auto 变量的初始值时,推断得到的类型是指针而非数组:

int ia[] = {0,1,2,3,4,5,6,7,8,9};
auto ia2(ia);                                    // ia2 是一个整型指针,指向 ia 的第一个元素
ia2 = 42;                                        // 错误:ia2 是一个指针,不能用 int 值给指针赋值//对于语句2 ,编译器实际执行的初始化过程类似下面这种形式:
auto ia2(&ia[0]);                                // 显然 ia2 的类型是 int*

必须指出的是,当使用 decltype 关键字时上述转换不会发生,decltype(ia) 返回的类型是由 10 个整数构成的数组:

// ia3 是一个含有 10 个整数的数组
decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 = p;                                          // 错误:不能用整型指针给数组赋值
ia3[4] = i;                                       // 正确:把 i 的值赋给 ia3 的一个元素

指针也是迭代器

​ 指针和迭代器一样,可以获取尾元素之后的那个不存在的元素地址:

int *e = &ia[10];
// 等价于
int *e = end(ia);

指针运算

只要两个指针指向同一个数组的元素,或者指向该数组的尾元素的下一个位置,就能利用关系运算符对其进行比较。例如可用如下方式遍历数组中的元素:

int *b = ia, int *e = ia + 10;
while (b < e) {++b;
}// 注意:如果两个指针分别指向不相关的对象,则不能比较它们

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

解引用和指针运算的交互

int ia[] = {0,2,4,6,8};
int last = *(ia + 4);                             // 正确:last = ia[4] = 8
last = *ia + 4;                                   // 正确:last = ia[0] + 4 = 4

下标和指针

虽然标准库类型 string 和 vector 也能执行下标运算,但是数组与它们相比还是有所不同。标准库类型限定使用的下标必须是无符号类型,而数组内置的下标运算无此要求,可以为负数。当然结果地址必须指向原来的指针所指同一数组中的元素(或是同一数组尾元素的下一位置)。

int *p  = &ia[2];                                 // p 指向索引为 2 的元素
int j = p[1];                                     // 等价于 *(p + 1),就是 ia[3] 表示的那个元素
int k = p[-2];                                    // k = ia[0]

3.6.0 多维数组

严格来说,C++ 语言中没有多维数组,通常所说的多维数组其实是数组的数组。谨记这一点,对今后理解和使用多维数组有大益处:

// 读法:从里到外,从左边往右读[]
int ia[3][4];                                      // 大小为 3 的数组,每个元素是含有 4 个整数的数组// 大小为 10 的数组,它的每个元素都是大小为 20 的数组
// 这些数组的元素是含有 30 个整数的数组
int arr[10][20][30] = {0};

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

// 遍历二维数组ia[3][4]
size_t cnt = 0;
for (auto &row : ia) {                             // 注意,此 & 不可省略,即使循环中没有如何读写操作for (auto &col : row) {                        // 当无读写操作时,此 & 可以省略/*    */}
}

​ 注意,对第一个for 语句一定得将控制变量声明成引用类型,这是为了避免数组被自动转成指针。第一个循环语句是遍历 ia 的所有元素,注意这些元素实际上是大小为 4 的数组。若 row 不是引用类型,编译器初始化 row 时会自动将这些数组形式的元素(和其他类型的数组一样)转换成指向该数组内首元素的指针。这样得到的 row 的类型就是 *int,显然内层的循环就不合法了。

指针和多维数组

因为多维数组实际上是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针:

int ia[3][4];                                      
int (*p)[4] = ia;                                  // p 为指针,指向含有 4 个整数的数组
p = &ia[2];                                        // p 指向 ia 的尾元素// 注意语句2 中的圆括号必不可少
int *p[4];                                         // 整型指针的数组

通过使用 auto 或者 decltype 就能尽可能地避免在数组面前加上一个指针类型了:

// 输出 ia 中每个元素的值,每个内层数组各占一行
// p 指向含有 4 个整数的数组
for (auto p = begin(ia); p != end(ia); ++p ) {// q 指向内层数组的首元素for (auto q = begin(*p); q != end(*p); ++q){cout<< *q << ' ';}cout<< endl;
}

题目

编写3个不同版本的程序,令其均能输出ia的元素。版本1使用范围for语句管理迭代过程;版本2和版本3都使用普通for语句,其中版本2要求使用下标运算符,版本3要求使用指针。此外,在所有3个版本的程序中都要直接写出数据类型,而不能使用类型别名、auto关键字和decltype关键字。

#include <iostream>
#include <cstddef>
#include <iterator>using std::cout;
using std::endl;
using std::begin;
using std::end;int main()
{int ia[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};for(const int (&i)[4]:ia)for(int j:i)cout << j << " ";cout << endl;for(size_t i = 0;i < 3;++i)for(size_t j = 0;j < 4;++j)cout << ia[i][j] << " ";cout << endl;for(int (*i)[4] = begin(ia);i != end(ia);i++)for(int *j = begin(*i);j != end(*i);j++)cout << *j << " ";cout << endl;return 0;
}

改写上一个练习中的程序,使用类型别名来代替循环控制变量的类型。

#include <iostream>
#include <cstddef>
#include <iterator>using std::cout;
using std::endl;
using std::begin;
using std::end;int main()
{typedef int int_array[4];                      // int_array 是 int [4] 的别名// using int_array = int[4];int ia[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};for(const int_array &i:ia)                     // 等价于 const int (&i)[4]:iafor(int j:i)cout << j << " ";cout << endl;for(size_t i = 0;i < 3;++i)for(size_t j = 0;j < 4;++j)cout << ia[i][j] << " ";cout << endl;for(int_array *i = begin(ia);i != end(ia);i++)for(int *j = begin(*i);j != end(*i);j++)cout << *j << " ";cout << endl;return 0;
}

再一次改写程序,这次使用auto关键字。

#include <iostream>
#include <cstddef>
#include <iterator>using std::cout;
using std::endl;
using std::begin;
using std::end;int main()
{int ia[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};for(const auto &i:ia)for(int j:i)cout << j << " ";cout << endl;for(size_t i = 0;i < 3;++i)for(size_t j = 0;j < 4;++j)cout << ia[i][j] << " ";cout << endl;for(auto *i = begin(ia);i != end(ia);i++)for(auto j = begin(*i);j != end(*i);j++)cout << *j << " ";cout << endl;return 0;
}

相关文章:

  • 麒麟系统mate_indicators进程占用内存资源高
  • c++的多态,继承,抽象类,虚函数表,虚函数等题目+分析
  • 5分钟了解单元测试
  • BUU CODE REVIEW 11 代码审计之反序列化知识
  • 【Python】类和对象的深入解析
  • C语言程序设计-11 结构体与共用体
  • (2024最新)CentOS 7上在线安装MySQL 5.7|喂饭级教程
  • Nginx基础理论
  • 智能温室大棚在无土栽培中的应用
  • MySQL:创建账户及修改密码
  • 在k8s中部署Elasticsearch高可用集群详细教程
  • Certificate数字证书的有效性验证
  • c#一个udp代码
  • asyncua模块中OPC UA的ua.Variant如何表示字典?
  • 四十八、openlayers地图调色总结——锐化、模糊、浮雕滤镜,调整地图色相、饱和度、亮度
  • 《Java编程思想》读书笔记-对象导论
  • Android交互
  • eclipse的离线汉化
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • Java多态
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • spring + angular 实现导出excel
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • 百度小程序遇到的问题
  • 第13期 DApp 榜单 :来,吃我这波安利
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 强力优化Rancher k8s中国区的使用体验
  • 驱动程序原理
  • 山寨一个 Promise
  • 微服务核心架构梳理
  • 线性表及其算法(java实现)
  • 在weex里面使用chart图表
  • 机器人开始自主学习,是人类福祉,还是定时炸弹? ...
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (二)PySpark3:SparkSQL编程
  • (接上一篇)前端弄一个变量实现点击次数在前端页面实时更新
  • (亲测有效)解决windows11无法使用1500000波特率的问题
  • (十八)Flink CEP 详解
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • ... 是什么 ?... 有什么用处?
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .NET Core WebAPI中封装Swagger配置
  • .NET Core 和 .NET Framework 中的 MEF2
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接
  • .net使用excel的cells对象没有value方法——学习.net的Excel工作表问题
  • .NET委托:一个关于C#的睡前故事
  • .Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)
  • .xml 下拉列表_RecyclerView嵌套recyclerview实现二级下拉列表,包含自定义IOS对话框...
  • @test注解_Spring 自定义注解你了解过吗?
  • @四年级家长,这条香港优才计划+华侨生联考捷径,一定要看!