C++ Primer 学习 -- Day 2

2024-06-20 00:52
文章标签 c++ primer day 学习

本文主要是介绍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;
}

这篇关于C++ Primer 学习 -- Day 2的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1076644

相关文章

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

51单片机学习记录———定时器

文章目录 前言一、定时器介绍二、STC89C52定时器资源三、定时器框图四、定时器模式五、定时器相关寄存器六、定时器练习 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示:以下是本篇文章正文内容,下面案例可供参考 一、定时器介绍 定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。 定时器作用: 1.用于计数系统,可

C++的模板(八):子系统

平常所见的大部分模板代码,模板所传的参数类型,到了模板里面,或实例化为对象,或嵌入模板内部结构中,或在模板内又派生了子类。不管怎样,最终他们在模板内,直接或间接,都实例化成对象了。 但这不是唯一的用法。试想一下。如果在模板内限制调用参数类型的构造函数会发生什么?参数类的对象在模板内无法构造。他们只能从模板的成员函数传入。模板不保存这些对象或者只保存他们的指针。因为构造函数被分离,这些指针在模板外

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

随想录 Day 69 并查集 107. 寻找存在的路径

随想录 Day 69 并查集 107. 寻找存在的路径 理论基础 int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好vector<int> father = vector<int> (n, 0); // C++里的一种数组结构// 并查集初始化void init() {for (int i = 0; i < n; ++i) {father[i] = i;}

AssetBundle学习笔记

AssetBundle是unity自定义的资源格式,通过调用引擎的资源打包接口对资源进行打包成.assetbundle格式的资源包。本文介绍了AssetBundle的生成,使用,加载,卸载以及Unity资源更新的一个基本步骤。 目录 1.定义: 2.AssetBundle的生成: 1)设置AssetBundle包的属性——通过编辑器界面 补充:分组策略 2)调用引擎接口API

C++工程编译链接错误汇总VisualStudio

目录 一些小的知识点 make工具 可以使用windows下的事件查看器崩溃的地方 dumpbin工具查看dll是32位还是64位的 _MSC_VER .cc 和.cpp 【VC++目录中的包含目录】 vs 【C/C++常规中的附加包含目录】——头文件所在目录如何怎么添加,添加了以后搜索头文件就会到这些个路径下搜索了 include<> 和 include"" WinMain 和

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J