这篇笔记是跟着菜鸟教程C++的复习笔记(不全面)

一、C++基础复习

这里只列出不熟悉的(或者是C中没有的语法)

1.三字符组

三字符组就是用于表示另一个字符的三个字符序列,又称为三字符序列。三字符序列总是以两个问号开头(现在很少使用)

三字符组 替换
??= #
??/ \
??’ ^
??( [
??) ]
??! |
??< {
??> }
??- ~

2.类型转换

  • 静态转换:强制转换为另外一种数据类型(不进行任何运行时类型检查)
1
2
int i = 10;
float f = static_cast<float>(i);//静态
  • 动态转换:通常用于将一个基类指针或引用转换为派生类指针或引用。动态转换在运行时进行类型检查,如果不能进行转换则返回空指针或引发异常
1
2
3
4
class Base {};
class Derived : public Base {};
Base * ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base);//将基类指针转换为派生类指针
  • 常量转换:用于将 const 类型的对象转换为非 const 类型的对象
1
2
cosnt int i = 10;
int& r = const_cast<int&>(i);//const int转换为int
  • 重新解释转换:将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换
1
2
int i = 10;
float f = reinterpret_cast<float&>(i);//int转换为float

当需要使用多个文件且只在其中一个文件中定义变量时(定义变量的文件在程序连接时是可用的),变量声明就显得非常有用。您可以使用 extern 关键字在任何地方声明一个变量。虽然您可以在 C++ 程序中多次声明一个变量,但变量只能在某个文件、函数或代码块中被定义一次

  • 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
  • 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边

在程序中,局部变量和全局变量的名称可以相同,但是在函数内,==局部变量的值会覆盖全局变量的值==,对于char的数据类型,使用'\0'进行初始化

块作用域指在代码块内部声明的变量,同样,块作用域内变量的值会覆盖外部作用域的变量

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int main() {
int a = 10;
{
int a = 20; // 块作用域变量
std::cout << "块变量: " << a << std::endl;
}
std::cout << "外部变量: " << a << std::endl;
return 0;
}

类作用域:在类内部声明的变量

常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量,推荐把常量定义为大写字母形式

字符常量是括在单引号中。如果常量以 L(仅当大写时)开头,则表示它是一个宽字符常量(例如 L’x’),此时它必须存储在 wchar_t 类型的变量中。否则,它就是一个窄字符常量(例如 ‘x’),此时它可以存储在 char 类型的简单变量中。

字符常量可以是一个普通的字符(例如 ‘x’)、一个转义序列(例如 ‘\t’),或一个通用的字符(例如 ‘\u02C0’)

mutable 说明符仅适用于类的对象,这将在本教程的最后进行讲解。它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改

thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁,每个线程都有其自己的变量副本。

thread_local 说明符可以与 static 或 extern 合并,可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义

类别 运算符 结合性
后缀 () [] -> . ++ - - 从左到右
一元 + - ! ~ ++ - - (type)* & sizeof 从右到左
乘除 * / % 从左到右
加减 + - 从左到右
移位 << >> 从左到右
关系 < <= > >= 从左到右
相等 == != 从左到右
位与 AND & 从左到右
位异或 XOR ^ 从左到右
位或 OR | 从左到右
逻辑与 AND && 从左到右
逻辑或 OR || 从左到右
条件 ?: 从右到左
赋值 = += -= *= /= %=>>= <<= &= ^= |= 从右到左
逗号 , 从左到右

lambda函数与表达式

Lambda 表达式把函数看作对象。Lambda 表达式可以像对象一样使用,比如可以将它们赋给变量和作为参数传递,还可以像函数一样对其求值。Lambda 表达式本质上与函数声明非常类似,具体形式如下:

1
[capture](parameters)->return-type{body}

引用与指针的不同之处:

  • 不存在空引用。引用必须连接到一块合法的内存
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化

引用通常用于函数参数列表和函数返回值

1
2
int i = 12;
int& r = i;//创建引用(引用可以被看做是变量附属与内存位置的第二个标签)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

int main()
{
int i = 12;
int& r = i;//r是一个初始化为i的整形引用
std::cout << "&i=" << &i << " " << "&r=" << &r << std::endl;
std::cout << "i=" << i << " " << "r=" << r << std::endl;
return 0;
}

//输出:(可以看出i和r地址相同)
&i=000000B990EFF544 &r=000000B990EFF544
i=12 r=12

cin空格后面的数据不会读入(如果有空格分隔则读取到空格处结束)

cerr为标准错误流:cerr 对象附属到标准输出设备,通常也是显示屏,但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出

clog为标准日志流:clog 对象附属到标准输出设备,通常也是显示屏,但是 clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出

二、C++面向对象

1.C++类&对象

类是 C++ 的核心特性,通常被称为用户定义的类型(==实际说白了就是C中的结构体,只不过类扩展了它,还包括可以执行哪些操作(个人理解)==)

定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作

声明类的对象,就像声明基本类型的变量一样。

只有类和友元函数可以访问私有成员,protected(受保护)成员在派生类(即子类)中是可访问的

1
2
3
4
5
6
class classname	//class 类名
{
Access specifiers: //访问修饰符:private/public/protected
Date members/variables; //变量
Member function(){} //方法
}; //类以分号结束

public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。

  • public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private(基类的非私有成员在子类的访问属性不变)
  • protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private(基类的非私有成员都为子类的保护成员)
  • private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private(基类中的非私有成员都称为子类的私有成员)

如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数

使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本

如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。

静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。静态成员函数有一个类范围,他们不能访问类的 this 指针。可以使用静态成员函数来判断类的某些对象是否已被创建

2.C++继承

一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:

class derived-class : access-specifier base-class

其中,访问修饰符 access-specifier 是 public、protectedprivate 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private

一个派生类继承了所有的基类方法,但下列情况除外:

  • 基类的构造函数、析构函数和拷贝构造函数。
  • 基类的重载运算符。
  • 基类的友元函数
  • 多继承
1
2
3
4
class <派生类名> : <继承方式1><基类名1>, <继承方式2><基类名2>,…
{
<派生类类体>
};

3.重载运算符和重载函数

  • 函数重载

在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数

  • 运算符重载

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表

4.C++多态

C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数

虚函数是在基类中使用关键字virtual声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数

纯虚函数:函数不用给出具体实现

定义方式:

virtual int area() = 0; =0告诉编译器函数没有主体,为纯虚函数

5.C++数据抽象和数据封装

数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。数据抽象是一种依赖于接口和实现分离的编程(设计)技术,可以说是像python调用库函数一样

封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏

数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制

6.C++接口

接口描述了类的行为和功能,而不需要完成类的特定实现。C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。

如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 “= 0” 来指定的

三、C++高级教程

1.C++异常

异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。

异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
  • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch

使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型

catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 ...

1
2
3
4
5
6
7
8
9
10
11
try
{
//保护代码
}catch(ExceptionName e1)
{
//catch块
}
catch(ExceptionName e2)
{
//catch块
}

2.C++动态内存

使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。

如果您不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存

malloc() 函数在 C++ 中仍然存在,但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。

2.1 new和delete运算符
1
2
data_type p = new data_type;	//使用new运算符为任意数据类型分配内存通用语法
delete p; //释放p指向的内存
2.2 数组的动态内存分配
1
2
3
4
5
6
//为数组动态分配内存
char* p =NULL;
p = new char[20];

//释放内存
delete [] p;

3.C++命名空间

命名空间的定义使用关键字 namespace,后跟命名空间的名称

调用带有命名空间的函数或变量,需要在前面加上命名空间的名称:name::code;code可以是变量或函数

使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称

1
2
3
4
namespace namespace_name
{
//代码声明
}

4.C++模版

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。模板是创建泛型类或函数的蓝图或公式

4.1 函数模板
1
2
3
4
5
6
7
//模版函数定义一般形式
//type 是函数所使用的数据类型的占位符名称, 这个名称可以在函数定义中使用
template <typename type>
ret_type func_name(parameter list)
{
//函数主体
}
4.2 类模板
1
2
3
4
5
6
7
//泛型类声明一般形式
//type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型
template <class type>
class class_name
{
...
}

5.C++预处理器

# 运算符会把 replacement-text 令牌转换为用引号引起来的字符串

## 运算符用于连接两个令牌

6.C++信号处理

下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号定义在 C++ 头文件 <csignal>

信号 描述
SIGABRT 程序的异常终止,如调用 abort
SIGFPE 错误的算术运算,比如除以零或导致溢出的操作。
SIGILL 检测非法指令。
SIGINT 程序终止(interrupt)信号。
SIGSEGV 非法访问内存。
SIGTERM 发送到程序的终止请求。
6.1 signal函数

注册信号及其信号处理函数

1
2
3
void (*signal (int sig, void (*func)(int)))(int);
//以下语法等同
signal(registered signal, signal handler);

这个函数接收两个参数:第一个参数是要设置的信号的标识符,第二个参数是指向信号处理函数的指针。函数返回值是一个指向先前信号处理函数的指针。如果先前没有设置信号处理函数,则返回值为 SIG_DFL。如果先前设置的信号处理函数为 SIG_IGN,则返回值为 SIG_IGN

6.2 raise函数

生成信号

这里sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP

1
int raise(signal sig);

7.C++多线程

多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,两种类型的多任务处理:基于进程和基于线程

  • 基于进程的多任务处理是程序的并发执行。
  • 基于线程的多任务处理是同一程序的片段的并发执行
7.1 创建线程
1
2
#include <pthread>
pthread_create(thread, attr, start_routine, arg);
7.2 终止线程

pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止

1
2
#include <pthread>
pthread_exit(status);
7.3 连接和分离线程

pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止。当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接

1
2
pthread_join (threadid, status) 
pthread_detach (threadid)
7.4 std::thread

C++ 11 之后添加了新的标准线程库 std::threadstd::thread 头文件中声明,因此使用 std::thread 时需要包含 在 头文件

std::thread默认构造函数创建一个空的std::thread执行对象

一个可调用对象可以是以下三个中的任何一个:

  • 函数指针
  • 函数对象
  • lambda 表达式

定义 callable 后,将其传递给 std::thread 构造函数 thread_object

1
2
#include <thread>
std::thread thread_object(callable);

8.C++ STL简单教程

C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈

C++ 标准模板库的核心包括以下三个组件:

组件 描述
容器(Containers) 容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。
算法(Algorithms) 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。
迭代器(iterators) 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。
  • push_back( ) 成员函数在向量的末尾插入值,如果有必要会扩展向量的大小。
  • size( ) 函数显示向量的大小。
  • begin( ) 函数返回一个指向向量开头的迭代器。
  • end( ) 函数返回一个指向向量末尾的迭代器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <vector>

int main()
{
//创建向量存储int
std::vector<int> vec;

//显示vec原始大小
std::cout << "vector size=" << vec.size() << std::endl;

//添加5个值到向量中
for (int i = 0; i < 5; i++)
{
vec.push_back(i);
}

//显示扩展后的大小
std::cout << "extend vector size=" << vec.size() << std::endl;

//访问vec
for (int i = 0; i < 5; i++)
{
std::cout << "value of vec[" << i << "]=" << vec[i] << std::endl;
}

//使用迭代器iterator访问
std::vector<int>::iterator v = vec.begin();
while (v!=vec.end())
{
std::cout << "value of v=" << *v << std::endl;
v++;
}
return 0;
}

9.C++标准库

C++ 标准库可以分为两部分:

  • 标准函数库: 这个库是由通用的、独立的、不属于任何类的函数组成的。函数库继承自 C 语言。
  • 面向对象类库: 这个库是类及其相关函数的集合。

C++ 标准库包含了所有的 C 标准库,为了支持类型安全,做了一定的添加和修改。

9.1 标准函数库

标准函数库分为以下几类:

  • 输入/输出 I/O
  • 字符串和字符处理
  • 数学
  • 时间、日期和本地化
  • 动态分配
  • 其他
  • 宽字符函数
9.2 面向对象类库

标准的 C++ 面向对象类库定义了大量支持一些常见操作的类,比如输入/输出 I/O、字符串处理、数值处理。面向对象类库包含以下内容:

  • 标准的 C++ I/O 类
  • String 类
  • 数值类
  • STL 容器类
  • STL 算法
  • STL 函数对象
  • STL 迭代器
  • STL 分配器
  • 本地化库
  • 异常处理类
  • 杂项支持库