1 关于n++/-- --/++n
n++
先使用n的值,再让n+1
++n
先n+1,再使用+1后的值
2 关于累加
- 一般写法
sum = sum + delta
- 简便写法
sum += delta
3 释放strdup申请的内存 C++申请/释放内存
- C
char *duplicate;
duplicate = strdup(out_folder.c_str());
strcpy(outfile, duplicate); //不安全,没释放strdup分配的内存 可定义中间变量释放
free(duplicate); //duplicate没有用malloc分配内存 但其同地址指向空间由malloc分配 所有可以用free释放
- C++
unsigned char *ucBuffer = new unsigned char[ulCount];
//处理
delete[] ucBuffer;
4 容器
4.1 容器begin & end
begin()
指向第一个元素 和end()
指向最后一元素的下一位置 返回迭代器,本质是指针,需用*解引用访问元素值
4.2 容器初始化
- 规定尺寸 每个元素都是 char 类型的默认值,通常是0或空字符('\0')
std::vector<char> buffer_(1024);
- 规定元素值 容器内只有单个元素且值为
std::vector<char> buffer_{1024};
- 其他初始化
- 字符串
char[5] = {} (全0) 或者 char[5] = ""; (全终止符\0)
- double 数组
double a[5] = {}; 全0
4.3 清空容器
buffer.clear()
容器内元素被移除,其size()为0,但分配的内存空间不变
4.4 删除容器内指定范围内元素
std::vector<int> vec = {1, 2, 3, 4, 5};
// 删除单个元素
vec.erase(vec.begin() + 2); // 删除索引为2的元素,即元素3
// 删除范围内的元素
vec.erase(vec.begin() + 1, vec.end() - 1); // 删除索引1到索引3的元素,即元素2和4
相应size()
也会减小
4.5 遍历容器
for (auto i = data_buffer_.begin(); i != data_buffer_.end(); i++)
5 C++方法访问限制
不声明保护类型struct 默认public
class 默认protected
6 C++多线程
- 不需要返回值
- 头文件
#include <thread>
// 创建多个线程,分别执行不同的函数
std::thread t1(function1, 其他参数);
std::thread t2(function2);
std::thread t3(function3);
// 等待所有线程执行完毕
t1.join();
t2.join();
t3.join();
std::cout << "所有函数已经完成执行。" << std::endl;
//将类的方法加入线程
boost::asio::io_service io_service;
std::string port = "COM8"; // 或 "COM1" 在 Windows 上
//1 创建线程 读取串口输入数据
ReadIMUFromSerial imu_receiver(io_service, port);
//2 创建一个工作对象来保持 io_service 活跃
boost::asio::io_service::work work(io_service);
std::thread readImu_thread(&ReadIMUFromSerial::startAsyncRead, &imu_receiver, 方法的参数); //如果传递参数为引用类型,需使用std::ref保护
- 需要返回值
- 头文件
#include <thread> <future>
// 使用std::async启动一个异步任务,并返回std::future对象
std::future<int> resultFuture = std::async(threadFunction, 其他参数);
// 在主线程中,我们可以使用get()来获取线程的返回值
int result = resultFuture.get();
std::cout << "线程返回的值是: " << result << std::endl;
在上面的代码中,std::async
用来启动一个异步任务,它返回一个std::future
对象,该对象包含了线程函数的返回值。当线程执行完毕后,我们可以通过调用std::future
对象的get()
方法来获取结果。请注意,如果线程还没有完成,调用get()
会阻塞主线程直到线程完成
- 将类的实例成员函数加入线程
// 创建类的实例
SerialReadReceiver read_receiver(/* 参数 */);
// 创建一个线程来运行成员函数
std::thread read_thread(&SerialReadReceiver::readData, &read_receiver /*, 成员函数的其他参数 */);
// 等待线程完成
read_thread.join();
特别注意
如果传入参数是引用,需使用std::ref(被保护的引用参数)
保护引用参数,否则只是值传递
7 变量访问控制锁
#include <mutex>
std::mutex data_mutex; // 全局互斥锁
void safe_increment(int& value) {
std::unique_lock<std::mutex> lock(data_mutex); // 加锁
++value; // 在这里,value 的访问是受保护的
// lock.unlock(); // 解锁,不过通常不需要手动调用,因为 unique_lock 会在离开作用域时自动解锁
}
std::unique_lock<std::mutex>
和 std::mutex
一起工作,以提供对共享数据的线程安全访问。当你创建一个 std::unique_lock<std::mutex>
对象时,它会尝试锁定与之关联的 std::mutex
。如果 std::mutex
已经被另一个线程锁定,当前线程将阻塞,直到它能够获得锁。
总结:给自己加锁A,如果锁A被其他线程占用,阻塞当前线程,直到获取锁
8 多线程协同机制
引入头文件
#include <condition_variable>
创建同步条件
std::condition_variable data_cond_var;
//线程同步
线程A
//进入线程A 执行操作
//操作完毕 唤醒线程B
data_cond_var.notify_one();
// 通知等待的线程数据已经准备好线程B
std::unique_lock<std::mutex> lock(data_mutex);
data_cond_var.wait(lock, [&data_process] { return !data_process.empty(); });
如果lambda条件不满足,或未被A唤醒, 线程B都将被阻塞
8.1 更多线程的访问控制
好的,C++ 中的线程管理和数据访问控制可以通过以下几种方式实现:
- 使用互斥锁(mutex)保护共享容器的访问。
- 使用条件变量(condition_variable)协调线程之间的通信。
下面是一个示例代码,展示了如何在C++中实现你描述的功能:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
std::mutex mtx_a, mtx_b, mtx_c;
std::condition_variable cv_a, cv_b;
std::queue<int> a;
std::queue<int> b;
std::queue<int> c;
bool done_a = false;
bool done_b = false;
void A() {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx_a);
a.push(i);
std::cout << "A pushed: " << i << std::endl;
cv_a.notify_one(); // Notify one waiting thread
}
std::unique_lock<std::mutex> lock(mtx_a);
done_a = true;
cv_a.notify_all(); // Notify all waiting threads
}
void B() {
while (true) {
std::unique_lock<std::mutex> lock_a(mtx_a);
cv_a.wait(lock_a, [] { return !a.empty() || done_a; });
if (a.empty() && done_a) break;
int data = a.front();
a.pop();
lock_a.unlock();
// Process data
int processed_data = data * 2;
std::unique_lock<std::mutex> lock_b(mtx_b);
b.push(processed_data);
std::cout << "B processed and pushed: " << processed_data << std::endl;
cv_b.notify_one();
}
std::unique_lock<std::mutex> lock_b(mtx_b);
done_b = true;
cv_b.notify_all();
}
void C() {
while (true) {
std::unique_lock<std::mutex> lock_b(mtx_b);
cv_b.wait(lock_b, [] { return !b.empty() || done_b; });
if (b.empty() && done_b) break;
int data = b.front();
b.pop();
lock_b.unlock();
// Further process data
int further_processed_data = data + 1;
std::unique_lock<std::mutex> lock_c(mtx_c);
c.push(further_processed_data);
std::cout << "C further processed and pushed: " << further_processed_data << std::endl;
}
}
int main() {
std::thread tA(A);
std::thread tB(B);
std::thread tC(C);
tA.join();
tB.join();
tC.join();
std::cout << "Final contents of c: ";
while (!c.empty()) {
std::cout << c.front() << " ";
c.pop();
}
std::cout << std::endl;
return 0;
}
代码解释:
-
共享数据结构:
- 使用
std::queue<int>
来表示共享容器a
,b
和c
。 - 使用互斥锁
std::mutex
来保护对共享容器的访问。 - 使用条件变量
std::condition_variable
来通知其他线程数据状态的改变。
- 使用
-
线程A:
- 向共享容器
a
中添加数据。 - 每次添加数据后使用
cv_a.notify_one()
通知可能在等待数据的线程B。
- 向共享容器
-
线程B:
- 从共享容器
a
中读取数据,进行预处理后将数据存入共享容器b
。 - 每次添加数据后使用
cv_b.notify_one()
通知可能在等待数据的线程C。
- 从共享容器
-
线程C:
- 从共享容器
b
中读取数据,进行进一步处理后将数据存入共享容器c
。
- 从共享容器
-
主函数:
- 启动三个线程,并在所有线程完成后输出最终容器
c
中的内容。
- 启动三个线程,并在所有线程完成后输出最终容器
这种设计确保了各个线程安全地访问共享数据,并通过条件变量实现了线程之间的同步。
9 打印不同数据类型为10进制
- unsigned int
%u
10 打印不同数据类型为指定进制
- 十六进制
%x - 八进制
%o
11 lambda 表达式的捕获
Lambda 表达式本质上是一个匿名函数对象,它可以捕获作用域中的变量,并且可以像普通函数一样被调用。当你使用 lambda 表达式时,编译器会自动为这个表达式生成一个类,这个类重载了 operator()
,使得这个 lambda 表达式成为一个可调用对象。
- 一般用法
serial_port_.async_read_some(boost::asio::buffer(buffer_),
[this, &data_process](const boost::system::error_code& error, size_t bytes_transferred) { //[捕获参数](lamda函数的输入传递参数){执行函数}
handleRead(error, bytes_transferred, data_process);
- 在类中捕获成员变量 捕获
this
就好了
data_cond_var.wait(lock, [this] { return !imuRaw.empty(); });
-
详细解释
在 C++ 中,lambda 表达式的捕获列表[]
用于指定 lambda 表达式中可以访问的外部变量。捕获列表中可以包含不同的捕获方式:
-
[]
- 不捕获任何外部变量。 -
[=]
- 以值捕获所有外部变量(按值传递)。 -
[&]
- 以引用捕获所有外部变量(按引用传递)。 -
[this]
- 以值捕获当前对象的指针this
。 -
[var]
- 以值捕获变量var
。 -
[&var]
- 以引用捕获变量var
。 -
[=, &var]
- 以值捕获所有变量,但var
以引用捕获。 -
[&, var]
- 以引用捕获所有变量,但var
以值捕获。
在 C++ 中,lambda 表达式的参数列表与普通函数的参数列表类似。它用来声明 lambda 表达式的参数。参数列表放在捕获列表 []
和主体 {}
之间。
auto lambda = [](参数列表) -> 返回类型 { // lambda 表达式体 };
例如:
#include <iostream>
int main() {
// 一个简单的 lambda 表达式,带有两个整数参数
auto add = [](int x, int y) -> int {
return x + y;
};
int result = add(3, 4);
std::cout << "Result: " << result << std::endl; // 输出 7
return 0;
}
这个 lambda 表达式有两个整数参数 x
和 y
,返回它们的和。
12 Eigen使用方法
- 创建矩阵
注意创建元素的数据类型
double:
Eigen::Matrix<double, 3, 3> double3D3;
或者
Eigen::MatrixXd mat(3, 3);
- 分割矩阵
mat.block(起始行索引、起始列索引、要提取的行数和列数);
- 给特定区块赋值
mat.block(起始行索引、起始列索引、要提取的行数和列数) = 赋值矩阵
或者
mat.block<要提取行数,要提取列数>(起始行索引,起始列索引) = 赋值矩阵
- rows()和row()
rows() 返回矩阵行数
row()mat.row(1); // 返回矩阵的第二行(索引从 0 开始
- 对每个元素取三角函数
在 Eigen 中,你可以使用.array()
方法将矩阵转换为数组表达式,然后对每个元素应用sin
函数。例如:
Eigen::Matrix3d A; // 创建一个 3x1 的矩阵
// 假设 A 已经被赋值
Eigen::Matrix3d B = A.array().sin(); // 对 A 中的每个元素取正弦值
这里,B
将是一个新的 3x1 矩阵,其元素是 A
中对应元素的正弦值。
Eigen中,凡是设计到对所有单个元素的计算,都需要使用array()转换成队列,如所有元素取平方,Matrix.array().square()
- 取对角元素
M是3*3矩阵
Eigen::MatrixXd D = M.diagonal().array().sqrt();
先取对角元素,再对每个元素开平方
- 行/列逆序重排
行:ztracemat(1, "行逆序重排\n", intmat.colwise().reverse());
列:ztracemat(1, "行逆序重排\n", intmat.rowwise().reverse());
- 对已创建矩阵重新赋值
已有矩阵
Eigen::MatrixXd M(3, 1);
M << 1, 2, 3;
1.可以使用block()
方法取块重新赋值
2.可以使用<< 填充元素
重新填充,元素数量务必等于M容量,否则导致错误
- 标量【 乘 除】Eigen矩阵
乘:
a * mat;
除:
a * mat.cwiseInverse();
- 对应元素相除
// 对应元素相除
Eigen::MatrixXd C = A.array() / B.array();
- 重新设置矩阵维度
mat.resize(rows, clos);
如果维度和设置前不同,会丢失所有元素值
- 索引规则
matlab
有矩阵:
1 2 3
4 5 6
7 8 9
索引 a(3) = 7; 列优先规则,1开始
Eigen
有矩阵:
1 2 3
4 5 6
7 8 9
索引 a(3) = 2; 列优先规则, 0开始
13 创建指向结构体的指针
在C++中,创建一个指向结构体的指针可以通过多种方式完成。对于你提到的 DD5
结构体,以下是一些创建指针的方法:
-
动态分配内存:
DD5* ptr = new DD5;
-
静态分配内存:
DD5 obj; DD5* ptr = &obj;
-
使用智能指针(如果你使用C++11或更高版本):
std::unique_ptr<DD5> ptr = std::make_unique<DD5>(); // 或者 std::shared_ptr<DD5> ptr = std::make_shared<DD5>();
第一种方法会在堆上分配内存,你需要在不再使用时手动删除它,使用 delete ptr;
。第二种方法会在栈上创建一个对象,并将指针指向它,无需手动管理内存。第三种方法使用智能指针,它会自动管理内存,当智能指针超出作用域时,它会自动释放分配的内存。
根据你的需要选择合适的方法。如果你在处理多线程或异步I/O,智能指针可能是一个更安全的选择,因为它们可以减少内存泄漏的风险。
14 结构内使用其他结构体的初始化问题
struct A
{
//构造器
A(int x, int y)
{
a1 = x;
a2 = y;
}
//成员变量
int a1;
int a2;
};
struct B
{
//构造器
B(int x, int y) : a(x, y)
{
b1 = x;
b2 = y;
}
// B(int x, int y)
// {
// a = A(x, y); 这是赋值操作,而不是初始化,初始化应在赋值操作之前,故而编译错误
// b1 = x;
// b2 = y;
// }
//成员变量
int b1;
int b2;
A a;
};
务必使用初始化列表对不含默认构造器的结构体变量进行初始化
因为执行代码时,会优先初始化成员变量,再执行构造函数
#include <iostream>
#include "struct.h"
int main() {
B b(1, 2);
A a(3, 4);
std::cout << "这是A:\n";
std::cout << a.a1 << " " << a.a2 << std::endl;
std::cout << "这是B:\n";
std::cout << b.a.a1 << " " << b.a.a2 << std::endl;
std::cout << "测试在初始化后使用构造器改变b中a的值\n";
b.a = A(5, 6);
std::cout << b.a.a1 << " " << b.a.a2 << std::endl;
return 0;
}
在后续使用中,可以使用 b.a = A(5, 6);更新成员的值
15 将函数作为参数传递
- 函数指针
函数定义:
void func(int a)
{
操作
}
将函数作为指针传递
void foo(void (*func)(int), int j) {
func(j);
}
使用foo:
int x=0;
foo(func(x));
- 使用
std::function
使用std::function
处理具有多个输入参数的函数,并且能够传递不同类型的可调用对象。
假设我们有一个函数,它接收两个int
参数并返回它们的和:
#include <iostream>
#include <functional>
// 定义一个函数,接收两个 int 参数并返回它们的和
int add(int a, int b) {
return a + b;
}
// 接受 std::function 作为参数的函数
void execute(std::function<int(int, int)> func, int x, int y) {
int result = func(x, y); // 调用传入的函数并传递参数
std::cout << "Result: " << result << std::endl;
}
int main() {
// 传递函数
execute(add, 3, 5);
// 传递 lambda 表达式
execute([](int a, int b) { return a * b; }, 3, 5);
// 传递函数对象
struct Multiply {
int operator()(int a, int b) const {
return a * b;
}
};
execute(Multiply(), 3, 5);
return 0;
}
16 左值引用和右值引用
- 左值引用(对原始对象的别名,二者指向同一内存地址)
左值引用用于引用左值(lvalue)。左值是指在内存中有确定存储地址的值,可以取地址运算符(&)。通常,左值是表达式的持久值,例如变量、数组元素等。左值引用的声明语法是在类型后加上&
。
int x = 10; // x 是一个左值
int& ref = x; // ref 是一个左值引用,引用了左值 x
ref = 20; // 修改 ref 相当于修改 x
std::cout << x; // 输出 20
- 右值引用(对原始对象资源的接管,原始对象变为有效但未定义状态)
右值引用用于引用右值(rvalue)。右值是指在内存中没有确定存储地址的值,通常是临时对象或字面值(如常量、表达式的结果等)。右值引用的声明语法是在类型后加上&&
。右值引用主要用于实现转移语义和完美转发,从而提高程序的性能。
int&& rref = 10; // rref 是一个右值引用,引用了右值 10
rref = 20; // 可以修改右值引用的值
std::cout << rref; // 输出 20
- 转移语义和std::move
右值引用的一个重要用途是实现转移语义。在标准库中,std::move
函数用于将左值转换为右值引用,从而启用转移语义。转移语义允许我们在不进行深拷贝的情况下,将资源从一个对象转移到另一个对象,大大提高了性能。
#include <iostream>
#include <utility>
#include <vector>
int main() {
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // 使用 std::move 将 v1 转换为右值引用
std::cout << "v1 size: " << v1.size() << std::endl; // 输出 0,v1 的资源被转移到 v2
std::cout << "v2 size: " << v2.size() << std::endl; // 输出 3,v2 获得了 v1 的资源
return 0;
}
在这个例子中,std::move
将 v1
转换为右值引用,允许 v2
接管v1
的资源,而不是进行深拷贝。转移语义提高了效率,尤其是在处理大量数据时。
特别注意
当你使用 std::move 将一个对象的资源转移到另一个对象时,被转移的对象(源对象)的资源会被移动到目标对象中。此时,源对象的资源会变为空或未定义状态。因此,对目标对象的修改不会影响源对象。
例如:
- 初始化
v1
为{1, 2, 3}
。 - 使用
std::vector<int> v2 = std::move(v1)
将v1
的资源转移到v2
。 - 此时,
v1
的资源已经被转移,通常变为空(size
为 0)。 - 修改
v2
的内容后,v1
的内容不受影响,因为v1
的资源已经被转移了。
因此,对v2
的修改不会同步反映到v1
上。
17 可变参数列表
- 函数模板
template<typename... Args>
- 函数传参
作为函数参数声明:bool ztrace(const int level, const std::string& format, Args... args);
作为函数参数输入:snprintf(buffer, sizeof(buffer), format.c_str(), args...); //格式化写入
作为模板输入:std::bind(std::forward<F>(f), std::forward<Args>(args)...); //绑定函数
18 函数模板
template<class F, class... Args>
void enqueue(F &&f, Args &&... args)
在 C++ 中,模板参数 class F
用于表示任何可以调用的类型,包括函数指针、函数对象以及 lambda 表达式。在 template<class F, class... Args>
中,F
是一个模板类型参数,它可以匹配任何类型的可调用对象,包括 lambda 表达式。
19 函数的默认参数设置
如果在h文件中的函数声明中已有默认输入参数,如:
void zquit(int signal_sum = 0); //退出程序
那对应cpp文件的定义中就不能有默认参数设置,会导致warning
void zquit(int signal_sum = 0)
{}
改为
void zquit()
{}
20 给数组名取地址得到的是什么?
在C和C++中,给数组名取地址会得到不同的结果,具体如下:
-
数组名本身:
- 数组名(如
arr
)在大多数情况下会自动转换为指向数组第一个元素的指针(即&arr[0]
)。例如:int arr[10]; int* p = arr; // 等价于 int* p = &arr[0];
- 数组名(如
-
数组名取地址:
- 给数组名取地址(如
&arr
)会得到一个指向整个数组的指针。这个指针的类型是int (*)[10]
,表示指向一个包含10个int
元素的数组。例如:int arr[10]; int (*p)[10] = &arr; // p 是一个指向包含10个int元素的数组的指针
- 给数组名取地址(如
-
区别:
-
arr
和&arr
在数值上是相同的,都是数组的起始地址,但它们的类型不同。arr
的类型是int*
,而&arr
的类型是int (*)[10]
。
-
21 cpp以静态库形式引用.c中定义的函数
C++会对所有的函数名做修饰以支持重载,重载后的函数名与.c中定义函数名不同,会出现未定义错误
对.c对应.h文件添加
#ifdef __cplusplus
extern "C" {
#endif
#include "v.h"
// 这里是一些 C 语言的声明
void myCFunction(int param);
#ifdef __cplusplus
}
#endif
取消对.c中函数名的修饰
22 引用的正确使用方式
数据类型 + & + 数据类型名
例如:
int& a;
int*& a;
23 智能指针
使用 std::shared_ptr
定义的指针在其引用计数变为零时会自动删除所管理的对象,因此通常不需要手动删除它们。不过,如果你想提前释放 std::shared_ptr
所管理的对象,可以使用 reset()
方法。以下是一些常见的方法:
1. 自动删除
当 std::shared_ptr
的引用计数变为零时,所管理的对象会自动被删除。例如:
{
std::shared_ptr<int> ptr = std::make_shared<int>(42);
// 当 ptr 超出作用域时,所管理的对象会被自动删除
}
2. 使用 reset()
方法
你可以使用 reset()
方法来提前释放 std::shared_ptr
所管理的对象:
std::shared_ptr<int> ptr = std::make_shared<int>(42);
ptr.reset(); // 释放所管理的对象,并将 ptr 置为空
3. 自定义删除器
如果你需要自定义删除行为,可以在创建 std::shared_ptr
时指定一个自定义删除器。例如:
std::shared_ptr<int> ptr(new int[10], [](int* p) {
delete[] p; // 自定义删除器,用于删除数组
});
ptr.reset(); // 调用自定义删除器,释放数组内存
示例代码
#include <iostream>
#include <memory>
void customDeleter(int* p) {
std::cout << "Deleting pointer with custom deleter" << std::endl;
delete[] p;
}
int main() {
std::shared_ptr<int> ptr(new int[10], customDeleter);
ptr.reset(); // 调用自定义删除器,释放数组内存
return 0;
}
24 虚方法和多态
定义类时,将类区分为基类和派生类,在基类中定义一些对象的基本方法和成员,同时使用虚方法修饰某些方法,表示这些方法可被派生类【覆盖】【继承】【继承+修改】
- 覆盖:使用
override
重写 - 继承:派生类不用写任何声明、实现,可直接使用基类方法(该方法不为纯虚方法)
- 继承+修改:
AnimalBase::eat(); + 其他派生类独有方法
如有基类 【AnimalBase.h】
#ifndef ANIMALBASE_H
#define ANIMALBASE_H
#include <iostream>
#include <string>
class AnimalBase
{
public:
AnimalBase(const std::string& inputName);
std::string getName() const;
virtual void eat(); //=0就是纯虚函数,不能实例化,只能被派生类重写
virtual ~AnimalBase(); // 声明虚析构函数
// 使用虚析构函数的原因是确保当删除派生类对象时,会先调用派生类的析构函数,然后再调用基类的析构函数。
// 如果析构函数不是虚函数,那么通过基类指针删除派生类对象时,只会调用基类的析构函数,可能导致资源泄漏。
private:
std::string name;
};
#endif /* ANIMALBASE_H */
【AnimalBase.cpp】的实现
#include "AnimalBase.h"
AnimalBase::AnimalBase(const std::string& inputName){
name = inputName;
}
std::string AnimalBase::getName() const {
return name;
}
void AnimalBase::eat(){
std::cout << "Eating..." << std::endl;
}
AnimalBase::~AnimalBase() {
// 可以为空,但需要定义
}
注意事项
1.有虚方法virtual void eat() = 0;
和 virtual void eat();
前者表示纯虚方法,派生类必须使用override
重写
后者表示一般虚方法,派生类可直接继承使用
- 定义一个类时,必要有构造器,可选析构器。如果声明了析构器,一定要写析构器的实现,否则使用语句
delete 对象
时会报错。
根据基类定义派生类
【Cat.h】
#ifndef CAT_H
#define CAT_H
#include "AnimalBase.h"
class Cat : public AnimalBase {
public:
Cat(const std::string& inputName) : AnimalBase(inputName) {} //派生类使用基类构造函数
void eat() override; //重写基类虚函数
};
class NormalCat : public AnimalBase {
public:
NormalCat(const std::string& inputName) : AnimalBase(inputName) {} //派生类使用基类构造函数
void eat(){
AnimalBase::eat(); //调用基类虚函数
std::cout << "Normal Cat eats meat." << std::endl;
}
};
#endif // CAT_H
【Cat.cpp】
#include "Cat.h"
void Cat::eat()
{
std::cout << "Cat is eating" << std::endl;
}
实例化
有两种实例化方法
1.Animal *pig = new Pig("pp");
Pig *pig = new Pig("pp");
①:
优势:可以调用不同派生类的方法(这些方法源自基类,可以被派生类重写),如
// 基类指针指向派生类对象
Animal* animals[2];
animals[0] = new Dog("Buddy");
animals[1] = new Cat("Whiskers");
// 通过基类指针调用派生类的重写函数
for (int i = 0; i < 2; ++i) {
animals[i]->speak();
}
【输出】:
Buddy says: Woof!
Whiskers says: Meow!
劣势:不能调用派生类的特有成员方法,如果你需要调用Pig
类特有的成员(在Animal
中没有定义的成员),需要先进行类型转换(例如,使用 dynamic_cast<Pig*>(pig)
)
②:
优势:你可以直接调用Pig
类的所有成员函数和属性,包括从 Animal
继承的成员和 Pig
类自己定义的成员
劣势:因为指针的静态类型是 Pig
,它不能直接用于操作其他类型的派生类对象。如果你有多个不同的派生类对象(如 Dog
, Cat
等),并且想要统一处理它们,使用这种方式会不太方便。
25 如何创建并使用枚举类型
在 C++ 中,枚举(
enum
)是一种用户定义的数据类型,它由一组命名的整型常量组成。枚举类型可以提高代码的可读性和可维护性。以下是创建和使用枚举类型的步骤:
创建枚举类型
你可以使用 enum
关键字来定义一个枚举类型。例如:
enum Color {
RED,
GREEN,
BLUE
};
在这个例子中,Color
是一个枚举类型,包含三个枚举常量:RED
、GREEN
和 BLUE
。默认情况下,RED
的值为 0,GREEN
的值为 1,BLUE
的值为 2。
使用枚举类型
你可以像使用其他数据类型一样使用枚举类型。例如:
Color myColor = RED;
if (myColor == RED) {
std::cout << "The color is red." << std::endl;
}
指定枚举常量的值
你也可以显式地指定枚举常量的值:
enum Color {
RED = 1,
GREEN = 2,
BLUE = 3
};
枚举类(C++11 及以上)
在 C++11 及以上版本中,你可以使用 enum class
来定义强类型枚举,这样可以避免命名冲突:
enum class Color {
RED,
GREEN,
BLUE
};
Color myColor = Color::RED;
if (myColor == Color::RED) {
std::cout << "The color is red." << std::endl;
}
使用 enum class
定义的枚举类型需要使用作用域解析运算符(::
)来访问枚举常量。
示例代码
以下是一个完整的示例,展示了如何创建和使用枚举类型:
#include <iostream>
enum class Color {
RED,
GREEN,
BLUE
};
int main() {
Color myColor = Color::RED;
if (myColor == Color::RED) {
std::cout << "The color is red." << std::endl;
} else if (myColor == Color::GREEN) {
std::cout << "The color is green." << std::endl;
} else if (myColor == Color::BLUE) {
std::cout << "The color is blue." << std::endl;
}
return 0;
}
这个示例程序将输出 "The color is red."。
26 构造器关键字 explicit
用于构造函数,防止隐式类型转换,当构造函数被标记为 explicit
时,它只能通过直接初始化来调用,而不能通过隐式转换来调用。这有助于避免意外的类型转换,从而提高代码的安全性和可读性。
举例:
class Example {
public:
explicit Example(int value) {
// 构造函数逻辑
}
};
Example ex1 = 10; // 错误:隐式转换被禁止
Example ex2(10); // 正确:直接初始化
27 两种构造器 a(p) 和a{p}
- a(p) 在单参数时 可能被解释为函数声明,而不是实例初始化,出现报错:
最令人困惑的解析(Most Vexing Parse)
- a{p} 花括号初始化或列表初始化 避免隐式转换,执行更严格的类型检查