知识点#
13.1 语言特性、工程构成与可移植性#
- @ 语言特性图
- ![[白板/P37 语言特性.canvas|P37 语言特性]]
13.1.1 语言特性:过程式、对象式程序设计#
[!tip] 面向过程式程序设计
- @ 面向过程: 从上到下,逐步求精,公用功能写成函数,需要结构体就定义结构体
- 代码按顺序一步一步解决问题
C++对过程式程序设计完全支持
[!tip] 基于对象的程序设计
- @ 基于对象: 把功能包到类中,定义类对象并通过该对象调用各种成员函数
C中的"结构" →C++中的"类"C中的"结构变量" →C++中的"对象"- 类比结构更强大:类中不仅可以定义成员变量,还可以定义成员函数(方法)
[!tip] 面向对象的程序设计
- @ 面向对象 = 基于对象 + 继承性 + 多态性
- 继承性: 子类继承父类的方法,并可增加自己的新成员函数
- 多态性: 父类和子类有同名成员函数,调用时根据具体情况决定调用哪个
- 优点:易维护、易扩展、模块化(通过访问级别限制访问)
13.1.2 C++程序和项目文件构成#
[!tip] 项目文件构成
- % 源文件:
.cpp后缀,也叫源文件- % 头文件:
.h后缀,公共定义放在头文件中(函数声明、类定义、#define等).hpp: 定义和实现都包含在一个文件里,减少编译次数C++标准头文件不带扩展名,如<cstdio>代替<stdio.h>
13.1.3 编译型语言概念与可移植性问题#
[!tip] 编译型 vs 解释型
- @ 编译型语言: 程序执行前需要专门编译过程,编译成二进制文件,执行时不需要重新翻译
- @ 解释型语言: 不进行预先编译,执行时先解释再执行
C++属于编译型语言,执行速度快- ! 可移植性是针对源代码而言的,相同源代码在不同操作系统上能编译运行并实现相同功能
13.2 命名空间简介与基本输入/输出精解#
- @ 命名空间图
- ![[白板/P38 命名空间与IO.canvas|P38 命名空间与IO]]
13.2.1 命名空间简介#
[!tip] 命名空间
- @ 命名空间: 为防止名字冲突而引入的机制
- 每个命名空间都有自己的名字,不可以同名
- 不同命名空间中的同名函数互不影响
namespace NMZhangSan // 定义命名空间
{
void radius()
{
printf("NMZhangSan::radius 函数被执行\n");
}
}
[!tip] 命名空间使用要点
- 1 访问命名空间中的实体:
命名空间名::实体名(::叫作用域运算符)- 1
using namespace 命名空间名;声明后可省略前缀- ! 同时
using namespace声明两个含同名函数的命名空间会报错- 解决方案:①不同时声明两个命名空间 ②不同命名空间函数不同名 ③调用时加前缀
13.2.2 基本输入/输出#
[!tip] C++ 输入/输出
- @
iostream库:C++标准库中的输入/输出流库#include <iostream>包含头文件(不带扩展名)std::cout: 标准输出对象,向屏幕输出内容<<: 输出运算符,将右侧内容写到cout(屏幕)中std::endl: 换行 + 刷新输出缓冲区
#include <iostream>
using namespace std;
int main()
{
int x = 3;
cout << x << "的平方是" << x * x << endl; // 3的平方是9
return 0;
}
[!tip]
std::endl的两个作用
- 1 输出换行符
\n- 1 刷新输出缓冲区(调用
flush,强制输出缓冲区中所有数据到屏幕)
[!tip] 基本输入
cin
std::cin: 标准输入对象>>: 输入运算符,从键盘读取数据
int a;
cin >> a; // 从键盘读入一个整数
13.3 自动类型推断、头文件防卫与引用#
13.3.1 自动类型推断#
[!tip] C++ 新标准初始化方式
C++11引入{}初始化:int abc{12};{}初始化更安全:int abc{3.5f};会编译报错(防止数据被误截断)()初始化也可以:int abc(12);
[!tip]
auto关键字
- @
auto: 变量的自动类型推断(C++11新含义)- 声明变量时根据初始值类型自动选择匹配类型
- 自动类型推断发生在编译期,不影响运行效率
- ! 定义
auto变量时必须给初始值
auto bvalue = true; // bool
auto ch = 'a'; // char
auto dv = 12; // int
auto ev = -5; // int
13.3.2 头文件防卫式声明#
[!tip] 头文件防卫式声明
- @ 防卫式声明: 使用
#ifndef、#define、#endif防止头文件被多次#include- ! 每个
.h头文件的#ifndef后面定义的名字都不一样
// head.h
#ifndef HEAD
#define HEAD
int g_globalh1 = 8;
#endif
13.3.3 引用#
[!tip] 引用
- @ 引用: 为变量起的另外一个名字(别名),用
&符号表示- 引用和原变量看成是同一个变量,不额外占用内存
- ! 定义引用时必须初始化
- ! 引用必须绑定到变量或对象上,不能绑定到常量
- ! 引用类型必须与原变量类型相同
int value = 10;
int &refval = value; // refval 是 value 的别名
refval = 13; // 等价于 value = 13
// 错误示例
int& refval2; // 错误!必须初始化
int& refval3 = 10; // 错误!不能绑定到常量
float& refbvalue = bvalue; // 错误!类型不同
[!tip] 引用 vs 取地址
int &b = a;— 引用,&在=左边int *p = &a;— 取地址符,&在=右边
[!tip] 引用作为函数形参
- 引用形参可以直接影响外界实参的值
- 不存在参数值复制问题,效率更高
void func(int &ta, int &tb) // 引用类型形参
{
ta = 4; // 直接影响 main() 中的 a
tb = 5; // 直接影响 main() 中的 b
}
13.3.4 常量#
[!tip]
const关键字
- @
const: 表示不变,定义变量时加const后值不可改变- 编译器会检查
const承诺- !
const变量实际上可以通过特殊手段修改,但强烈建议不要这样做
const int var = 17; // 承诺值不变
// var = 18; // 编译报错
// 技术上可以修改(但不建议)
int &var2 = (int&)var;
var2 = 5; // var 的值确实变成了 5
[!tip]
constexpr关键字
- @
constexpr(C++11): 在编译时求值的常量,提升运行时性能constexpr函数中代码必须尽可能简单- !
constexpr函数中不能定义未初始化变量- !
constexpr函数中不能有printf等语句constexpr自带inline属性
constexpr int var1 = 1;
constexpr int func1(int abc) // constexpr 函数
{
return abc * 16;
}
constexpr int var2 = 11 * func1(12); // 编译时求值
13.4 范围 for、new 内存动态分配与 nullptr#
13.4.1 范围 for 语句#
[!tip] 范围
for语句
- @ 范围
for(C++11): 用于遍历一个序列- 使用
auto自动推断元素类型- 使用
auto &引用方式避免数据复制,提高效率
int v[] = {12, 13, 14, 16, 18};
for (auto x : v) // 复制元素到 x
cout << x << endl;
for (auto &x : v) // 引用方式,避免复制
cout << x << endl;
13.4.2 动态内存分配问题#
[!tip] C++ 内存 5 个区域
- % 栈: 函数内局部变量,编译器自动分配和释放
- % 堆: 程序员用
malloc/new申请,free/delete释放- % 全局/静态存储区: 全局变量和静态变量,程序结束时释放
- % 常量存储区: 存放常量,不允许修改
- % 程序代码区: 保存程序代码
| 对比项 | 栈 | 堆 |
|---|---|---|
| 空间大小 | 有限(系统规定) | 理论上不超出物理内存 |
| 分配速度 | 快 | 相对较慢 |
| 控制权 | 程序员控制不了 | 程序员自由决定 |
| 分配方式 | 自动 | new/malloc 手动 |
[!tip]
malloc和free
C语言中的函数,成对使用,从堆中分配和释放内存malloc返回void *类型,分配失败返回NULL- ! 使用完必须
free,否则内存泄漏
int *p = NULL;
p = (int *)malloc(10 * sizeof(int)); // 分配40字节
if (p != NULL)
{
*p = 5;
cout << *p << endl;
free(p); // 千万不要忘记!
}
[!tip]
new和delete
C++中的运算符(不是函数),成对使用new不但分配内存,还会做初始化工作delete不但释放内存,还会做清理工作- ! 在
C++中,不要再使用malloc/free,而是使用new/delete
int *myint = new int; // 分配一个 int 空间
*myint = 8;
delete myint; // 释放
int *myint2 = new int(18); // 分配并初始化为 18
delete myint2;
int *a = new int[100]; // 分配大小为 100 的数组
delete[] a; // ! 释放数组要用 delete[]
[!tip]
malloc/freevsnew/delete
new/delete做了malloc/free同样的事情,还做了更多new会额外做初始化,delete会额外做清理- ! 有
new必有delete,有malloc必有free- !
free/delete不要重复调用
13.4.3 nullptr#
[!tip]
nullptr
- @
nullptr(C++11): 代表空指针的新关键字NULL实际就是0,类型是intnullptr类型是std::nullptr_t- !
NULL和nullptr类型不同,在函数重载时会导致调用不同函数- ! 对于指针初始化,能用
nullptr的全部用nullptr
void myfunc(void *ptmp) { printf("void *\n"); }
void myfunc(int tmpvalue) { printf("int\n"); }
myfunc(NULL); // 调用 myfunc(int)
myfunc(nullptr); // 调用 myfunc(void *)
13.5 结构、类与组织#
13.5.1 结构回顾#
[!tip] C++ 中的结构
C++中结构可以省略struct关键字C++中的结构不仅可以有成员变量,还可以有成员函数- 成员函数用
对象名.成员函数名(实参列表)调用
struct student
{
int number; // 成员变量
char name[100]; // 成员变量
void func() // 成员函数
{
number++;
}
};
student student1;
student1.number = 1001;
student1.func(); // 调用成员函数
[!tip] 结构体作为函数参数
- 值传递:效率低(参数值复制)
- 引用传递:效率高,形参改变影响实参
- 指针传递:效率高,通过
->访问成员
13.5.2 public 和 private 权限修饰符#
[!tip] 权限修饰符
- @
public: 公有,成员可以被外界访问(外部接口)- @
private: 私有,成员只能被类内部定义的成员函数使用struct默认所有成员为publicclass默认所有成员为private- 判断成员权限:沿该成员往上方看,先看到哪个修饰符就属于哪个
struct student
{
public:
int number; // public
private:
char name[100]; // private
public:
void func() // public
{
number++; // 成员函数可以访问 private 成员
}
};
13.5.3 类简介#
[!tip] 类与结构的区别
- 类用
class定义,结构用struct定义class默认访问级别为private,struct默认为publicclass默认继承为private,struct默认继承为public- 如果都明确写上访问级别,
struct和class没什么区别
[!tip] 类的核心要点
C++标准库包含大量丰富的类和函数- ! 在
C++中,无论实现什么功能,都应设法通过写一个或多个类来达到目的- 类是
C++语言中最核心的部件
13.5.4 类的组织#
[!tip] 类的代码组织
- 1 类的定义代码放到
.h头文件中(头文件名与类名相同)- 1 类的实现代码(成员函数实现)放到
.cpp文件中- 1 其他文件使用类时,
#include "类名.h"包含头文件
13.6 函数新特性、inline 内联函数与 const 详解#
13.6.1 函数回顾与后置返回类型#
[!tip] 后置返回类型
- @ 前置返回类型: 返回类型在函数声明开头(传统写法)
- @ 后置返回类型(
C++11): 返回类型写在参数列表之后,用->开始- 前面放
auto关键字
// 前置返回类型
int func(int a, int b);
// 后置返回类型
auto func(int a, int b) -> int;
13.6.2 inline 内联函数#
[!tip]
inline内联函数
- @ 内联函数: 在编译阶段将函数调用替换为函数本体,提升执行性能
inline只是给编译器的建议,编译器可以不采纳- 内联函数定义放在头文件中
- ! 内联函数体要尽可能短小
- ! 复杂的循环、分支、递归不应出现在内联函数中
constexpr函数自带inline属性
[!tip] 内联函数优缺点
- $ 优点:避免函数调用开销(压栈、出栈),增加效率
- $ 缺点:代码膨胀,多处调用导致多处重复代码
13.6.3 函数特殊写法总结#
[!tip] 函数特殊写法
- 返回
void的函数可以作为另一个void函数的返回值:return funcA();- ! 不要返回局部变量的指针或引用(局部变量函数结束后被销毁)
C++中更习惯使用引用类型形参取代指针类型形参- 函数重载:同名函数形参类型或数量应有明显区别
- !
const关键字在比较同名函数时会被忽略,不能仅靠const区分重载
13.6.4 const char *、char const * 与 char * const 三者的区别#
[!tip] 指针与
const三种写法
- @
const char *p: 指向常量的指针,*p不能改(指向的内容不能改),p能改(指向能改)- @
char const *p: 与const char *p完全等价- @
char * const p: 常量指针,p不能改(指向不能改),*p能改(指向的内容能改)- 记忆技巧:
const在*左边修饰内容,const在*右边修饰指针
| 写法 | 指向能改 | 内容能改 | 含义 |
|---|---|---|---|
const char *p | 能 | 不能 | 指向常量字符 |
char const *p | 能 | 不能 | 同上 |
char * const p | 不能 | 能 | 常量指针 |
13.6.5 函数形参中带 const#
[!tip] 函数形参使用
const
- ! 防止无意中修改形参值导致实参值被修改
const形参可以接收更多类型的实参(常量引用和普通引用都能接收)- 建议形参使用常量引用
const 类型 &
void fs(const student &stu) // 常量引用形参
{
// stu.num = 1010; // 错误!不能修改
}
void func2(const int &a) {}
func2(156); // 可以传递常量
13.7 string 类型#
- @ string 类型图
- ![[白板/P39 string类型.canvas|P39 string类型]]
13.7.1 总述#
[!tip]
string类型
- @
string: 标准库中的类型,代表可变长字符串- 位于
std命名空间中#include <string>包含头文件- 尽量使用标准库提供的功能,不要重复开发
13.7.2 定义和初始化 string 对象#
string s1; // 默认初始化,空串
string s2 = "I love China"; // 复制初始化
string s3("I love China"); // 直接初始化
string s4 = s2; // 拷贝初始化
string s5(6, 'a'); // 6个字符'a'组成的字符串
13.7.3 string 对象上的常用操作#
[!tip]
string常用操作
| 操作 | 说明 | 示例 |
|---|---|---|
s.empty() | 判断是否为空 | if (s1.empty()) |
s.size() / s.length() | 返回字符数(字节数) | s2.size() |
s[n] | 返回第 n 个字符(从 0 开始) | s3[4] |
s1 + s2 | 字符串连接 | s6 = s4 + s5 |
s1 = s2 | 赋值 | s7 = s8 |
s1 == s2 | 判断相等(大小写敏感) | if (s9 == s10) |
s1 != s2 | 判断不等 | if (s9 != s10) |
s.c_str() | 返回 C 风格字符串指针 | const char *p = s.c_str() |
string s3 = "I love China";
s3[4] = 'w'; // 修改字符
cout << s3 << endl; // I wove China
// c_str() 与 C 语言兼容
const char *p = s10.c_str();
char str[100];
strcpy_s(str, sizeof(str), p);
[!tip]
string读写与字面值相加
cin >> s1读入字符串(遇空格停止)- ! 两个字符串字面值不能直接相加:
"abc" + "def"语法错误- 正确写法:
"abc" + s1 + "def"(中间夹一个string对象)
[!tip] 范围
for遍历string
string s1 = "I love China";
for (auto &c : s1) // 引用方式,可修改
c = toupper(c); // 小写转大写
cout << s1 << endl; // I LOVE CHINA
13.8 vector 类型#
- @ vector 类型图
- ![[白板/P40 vector类型.canvas|P40 vector类型]]
13.8.1 vector 类型简介#
[!tip]
vector类型
- @
vector: 标准库中的容器/动态数组类型#include <vector>包含头文件vector<int> vjihe;定义存放int的容器vector是类模板,<int>提供元素类型信息- !
vector不能装引用(引用不是对象)
vector<int> vjihe; // 存放 int
vector<string> mystr; // 存放 string
vector<student> studlist; // 存放自定义结构
// vector<int&> vjihe3; // 错误!不能装引用
13.8.2 定义和初始化 vector 对象#
vector<string> mystr; // 空 vector
vector<string> mystr2(mystr); // 复制元素
vector<string> mystr3 = mystr; // 复制元素
vector<string> def{"aaa", "bbb", "ccc"}; // 初始化列表
vector<int> ijihe(15, -200); // 15个元素,每个值为 -200
vector<int> ijihe2(20); // 20个元素,每个值为 0
[!tip]
()vs{}初始化
()一般表示元素数量{}一般表示元素内容vector<int> i1(10);— 10个元素,每个为 0vector<int> i2{10};— 1个元素,值为 10
13.8.3 vector 对象上的操作#
[!tip]
vector常用操作
| 操作 | 说明 |
|---|---|
v.empty() | 判断是否为空 |
v.push_back(val) | 向末尾增加一个元素 |
v.size() | 返回元素个数 |
v.clear() | 移除所有元素 |
v[n] | 返回第 n 个元素(从 0 开始) |
v1 = v2 | 赋值,用 v2 内容取代 v1 |
v1 == v2 | 判断相等 |
v1 != v2 | 判断不等 |
vector<int> ivec;
ivec.push_back(1);
ivec.push_back(2);
cout << ivec.size() << endl; // 2
// 范围 for 遍历
for (auto &vecitem : vecvalue)
vecitem *= 2; // 元素值翻倍
[!tip] 范围
for中不要改变vector容量
- ! 在范围
for循环中,不要增加或删除vector中的元素- 增删元素会导致序列结束位置改变,输出混乱
- ! 切记:在
for语句中,不要改变vector的容量
13.9 迭代器精彩演绎、失效分析及弥补、实战#
13.9.1 迭代器简介#
[!tip] 迭代器
- @ 迭代器: 遍历容器内元素的数据类型,类似指针
C++中一般采用迭代器来访问容器元素,而非下标- 所有容器都支持迭代器操作
13.9.2 容器的迭代器类型#
vector<int> iv{100, 200, 300};
vector<int>::iterator iter; // 定义迭代器
13.9.3 迭代器 begin/end、反向迭代器 rbegin/rend#
[!tip] 迭代器操作
begin(): 返回指向容器第一个元素的迭代器end(): 返回指向容器末端元素后面的迭代器(哨兵)- 容器为空时,
begin()和end()返回的迭代器相同rbegin(): 反向迭代器,指向最后一个元素rend(): 反向迭代器,指向第一个元素的前面
// 正向遍历
vector<int> iv{100, 200, 300};
for (vector<int>::iterator iter = iv.begin(); iter != iv.end(); ++iter)
cout << *iter << endl; // 100 200 300
// 反向遍历
for (vector<int>::reverse_iterator riter = iv.rbegin(); riter != iv.rend(); ++riter)
cout << *riter << endl; // 300 200 100
13.9.4 迭代器运算符#
| 运算符 | 说明 |
|---|---|
*iter | 返回迭代器指向元素的引用 |
++iter | 指向下一个元素 |
--iter | 指向上一个元素 |
iter1 == iter2 | 判断两个迭代器是否相等 |
iter->member | 访问结构成员(等价于 (*iter).member) |
13.9.5 const_iterator 迭代器#
[!tip]
const_iterator
- @
const_iterator: 只读迭代器,只能读元素,不能修改- 迭代器本身可以改变(指向下一个元素),但指向的元素值不能改
- 常量容器必须使用
const_iteratorcbegin()/cend()(C++11): 始终返回const_iterator
const vector<int> iv{100, 200, 300};
vector<int>::const_iterator iter;
for (iter = iv.begin(); iter != iv.end(); ++iter)
// *iter = 4; // 错误!不能修改
cout << *iter << endl; // 可以读
13.9.6 迭代器失效#
[!tip] 迭代器失效
- ! 任何能改变
vector对象容量的操作(如push_back)都会使当前迭代器失效- 在使用迭代器的循环中,千万不要改变
vector的容量- ! 插入一个元素后应立刻
break,重新获取begin/end- ! 删除元素时,
erase返回下一个元素位置
// 安全的删除方式
while (!iv.empty())
{
auto iter = iv.cbegin();
iv.erase(iter);
}
// 安全的连续插入方式
auto beg = vecvalue.begin();
int icount = 0;
while (beg != vecvalue.end())
{
beg = vecvalue.insert(beg, icount + 80); // insert 返回结果要保存
icount++;
if (icount > 10) break;
++beg;
}
13.10 类型转换#
13.10.1 类型转换种类#
[!tip]
C++四种类型转换运算符
| 运算符 | 用途 | 检查时机 |
|---|---|---|
static_cast | 相关类型转换 | 编译时 |
dynamic_cast | 父类转子类(运行时类型识别) | 运行时 |
const_cast | 去除 const 属性 | 编译时 |
reinterpret_cast | 无关类型转换(重新解释) | 编译时 |
13.10.2 static_cast 详解#
[!tip]
static_cast
- 用于相关类型转换(整型↔实型、子类→父类、
void *↔ 其他指针)- 不能用于指针类型之间的转换(如
int *→double *)
double f = 100.34f;
int i2 = static_cast<int>(f); // 100, C++ 风格
// void * 与其他指针转换
int i = 10;
int *p = &i;
void *q = static_cast<void*>(p);
int *db = static_cast<int*>(q);
13.10.3 const_cast 详解#
[!tip]
const_cast
- 唯一能去掉
const属性的转换- ! 如果原来是常量,强行去掉
const并写入是未定义行为- ! 使用
const_cast总是意味着设计缺陷
const int ai = 90;
const int *pai = &ai;
int *pai2 = const_cast<int*>(pai); // 去掉 const
// *pai2 = 120; // 未定义行为!不要这样做
13.10.4 reinterpret_cast 详解#
[!tip]
reinterpret_cast
- 用于无关类型转换,重新解释内存内容
- ! 非常危险的类型转换,安全性很差
- 不建议轻易使用
13.10.5 总结#
Reply by Email[!tip] 类型转换总结
- ! 强制类型转换一般不建议使用,会干扰系统正常类型检查
- 写
C++程序时不要使用老式C风格类型转换,全部用新风格static_cast和reinterpret_cast能很好地取代C风格类型转换- 使用
reinterpret_cast非常危险,使用const_cast总是意味着设计缺陷

