跳过正文
P12 C++基本语言
  1. S/
  2. C/C++/
  3. C/
  4. C语言程序设计/

P12 C++基本语言

Li
作者
Li
往前走,别回头!
目录
C 语言程序设计笔记 - 这篇文章属于一个选集。

知识点
#

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 关键字

  • @ constexprC++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 语句

  • @ 范围 forC++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] mallocfree

  • 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] newdelete

  • 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/free vs new/delete

  • new/delete 做了 malloc/free 同样的事情,还做了更多
  • new 会额外做初始化,delete 会额外做清理
  • ! 有 new 必有 delete,有 malloc 必有 free
  • ! free/delete 不要重复调用

13.4.3 nullptr
#

[!tip] nullptr

  • @ nullptrC++11): 代表空指针的新关键字
  • NULL 实际就是 0,类型是 int
  • nullptr 类型是 std::nullptr_t
  • ! NULLnullptr 类型不同,在函数重载时会导致调用不同函数
  • ! 对于指针初始化,能用 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 publicprivate 权限修饰符
#

[!tip] 权限修饰符

  • @ public: 公有,成员可以被外界访问(外部接口)
  • @ private: 私有,成员只能被类内部定义的成员函数使用
  • struct 默认所有成员为 public
  • class 默认所有成员为 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 默认访问级别为 privatestruct 默认为 public
  • class 默认继承为 privatestruct 默认继承为 public
  • 如果都明确写上访问级别,structclass 没什么区别

[!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个元素,每个为 0
  • vector<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_iterator
  • cbegin()/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 总结
#

[!tip] 类型转换总结

  • ! 强制类型转换一般不建议使用,会干扰系统正常类型检查
  • C++ 程序时不要使用老式 C 风格类型转换,全部用新风格
  • static_castreinterpret_cast 能很好地取代 C 风格类型转换
  • 使用 reinterpret_cast 非常危险,使用 const_cast 总是意味着设计缺陷
Reply by Email
C 语言程序设计笔记 - 这篇文章属于一个选集。

相关文章