C++学习笔记五
C++继承
//基类
class Animal{};//派生类
class Dog : public Animal{};
#include<iostearm>
using namespace std;//基类
class Shape{public:void setwidth(int w){width = w;}void setheight(int h){height = h;}protected:int width;int height;}//派生类
class Rectangle : public Shape{public:int getarea(){return width * height;}}int main(void){Rectangle Rect;Rect.setwidth(5);Rect.setheight(7);// 输出对象的面积cout << "Total area: " << Rect.getArea() << endl;return 0;}
访问控制与继承
多继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
C++ 类可以从多个类继承成员
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
C++重载运算符和重载函数
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
当调用一个重载函数或重载运算符时,编译器通过把所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
函数重载
下面的实例中,同名函数 print() 被用于输出不同的数据类型
#include<iostearm>
using namespace std;class printdata{public:void print(int i) {cout << "整数为: " << i << endl;}void print(double f) {cout << "浮点数为: " << f << endl;}void print(char c[]) {cout << "字符串为: " << c << endl;}}int main(void)
{printdata pd;// 输出整数pd.print(5);// 输出浮点数pd.print(500.263);// 输出字符串char c[] = "Hello C++";pd.print(c);return 0;
}
运算符重载
Box operator+(const Box&);
Box operator+(const Box&, const Box&);
#include <iostream>
using namespace std;class Box
{public:double getVolume(void){return length * breadth * height;}void setLength( double len ){length = len;}void setBreadth( double bre ){breadth = bre;}void setHeight( double hei ){height = hei;}// 重载 + 运算符,用于把两个 Box 对象相加Box operator+(const Box& b){Box box;box.length = this->length + b.length;box.breadth = this->breadth + b.breadth;box.height = this->height + b.height;return box;}private:double length; // 长度double breadth; // 宽度double height; // 高度
};
// 程序的主函数
int main( )
{Box Box1; // 声明 Box1,类型为 BoxBox Box2; // 声明 Box2,类型为 BoxBox Box3; // 声明 Box3,类型为 Boxdouble volume = 0.0; // 把体积存储在该变量中// Box1 详述Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0);// Box2 详述Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0);// Box1 的体积volume = Box1.getVolume();cout << "Volume of Box1 : " << volume <<endl;// Box2 的体积volume = Box2.getVolume();cout << "Volume of Box2 : " << volume <<endl;// 把两个对象相加,得到 Box3Box3 = Box1 + Box2;// Box3 的体积volume = Box3.getVolume();cout << "Volume of Box3 : " << volume <<endl;return 0;
}
C++多态
当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态允许使用基类指针或引用来调用子类的重写方法,从而使得同一接口可以表现不同的行为。多态使得代码更加灵活和通用,程序可以通过基类指针或引用来操作不同类型的对象,而不需要显式区分对象类型。这样可以使代码更具扩展性,在增加新的形状类时不需要修改主程序。
例如,假设有一个基类 Shape
和派生类 Circle
、Rectangle
,它们都实现了 draw()
方法。通过多态,我们可以统一调用 draw()
方法,而具体绘制的是圆形还是矩形则由对象的实际类型决定。
class Math {
public:int add(int a, int b) { return a + b; } // 整数加法double add(double a, double b) { return a + b; } // 浮点数加法
};// 使用示例
Math m;
m.add(1, 2); // 调用 add(int, int)
m.add(1.5, 2.5); // 调用 add(double, double)
C++数据抽象
C++ 类为数据抽象提供了可能。它们向外界提供了大量用于操作对象数据的公共方法,也就是说,外界实际上并不清楚类的内部实现。实现了内部与外部的分离
#include<iostearm>
using namespace std;int main(){cout << "hello world" << endl;//此处并不需要cout是如何实现的,调用即可return 0;}
实例(有成员 addNum 和 getTotal 是对外的接口,用户需要知道它们以便使用类。私有成员 total 是用户不需要了解的,但又是类能正常工作所必需的):
#include<iostearm>
using namespace std;class Adder{public://构造函数Adder(int i = 0){total = i;}//对外的接口void addNum(int number){total += number;}//对外的接口int gettotal(){return total;}private://对外隐藏的数据int total;}int main(){Adder a;a.addNum(10);a.addNum(20);a.addNum(30);cout << "total is " << a.gettotal() << endl;return 0;}
C++接口(抽象类)
接口描述了类的行为和功能,而不需要完成类的特定实现。
C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的
class BOX
{public://纯虚函数virtual double getvolume() = 0;private:double length;double breadth;double height;}
设计抽象类(通常称为 ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。
C++文件和流
C++ 中另一个标准库 fstream,它定义了三个新的数据类型:
要在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件 <iostream> 和 <fstream>。
open() 成员函数的第一参数指定要打开的文件的名称和位置,第二个参数定义文件被打开的模式。
void open(const char *filename, ios::openmode mode);
可以把以上两种或两种以上的模式结合使用。例如,如果想要以写入模式打开文件,并希望截断文件,以防文件已存在,那么可以使用下面的语法:
ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc );
如果想要打开一个文件用于读写
ifstream afile;
afile.open("file.dat", ios::out | ios::in );
写入文件
在 C++ 编程中,我们使用流插入运算符( << )向文件写入信息,就像使用该运算符输出信息到屏幕上一样。唯一不同的是,在这里您使用的是 ofstream 或 fstream 对象,而不是 cout 对象。
读取文件
在 C++ 编程中,我们使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用的是 ifstream 或 fstream 对象,而不是 cin 对象。
C++异常处理
异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw。
- throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
- catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
- try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
try
{// 保护代码
}catch( ExceptionName e1 )
{// catch 块
}catch( ExceptionName e2 )
{// catch 块
}catch( ExceptionName eN )
{// catch 块
}
double division(int a, int b)
{if( b == 0 ){throw "Division by zero condition!";}return (a/b);
}
C++动态内存
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存
可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。
不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。
new和delete运算符
我们可以定义一个指向 double 类型的指针,然后请求内存,该内存在执行时被分配
double* pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存
如果自由存储区已被用完,可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针,并采取以下适当的操作:
double* pvalue = NULL;
if( !(pvalue = new double ))
{cout << "Error: out of memory." <<endl;exit(1);}
在任何时候,当您觉得某个已经动态分配内存的变量不再需要使用时,您可以使用 delete 操作符释放它所占用的内存,如下所示:
delete pvalue; // 释放 pvalue 所指向的内存
数组的动态内存分配
假设我们要为一个字符数组(一个有 20 个字符的字符串)分配内存,我们可以使用上面实例中的语法来为数组动态地分配内存,如下所示:
char* pvalue = NULL; // 初始化为 null 的指针
pvalue = new char[20]; // 为变量请求内存
要删除我们刚才创建的数组,语句如下
delete [] pvalue; // 删除 pvalue 所指向的数组
C++预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
#define 预处理
用于创建符号常量。该符号常量通常称为宏
#include <iostream>
using namespace std;#define PI 3.14159int main ()
{cout << "Value of PI :" << PI << endl; return 0;
}
参数宏
可以使用 #define 来定义一个带有参数的宏
#include <iostream>
using namespace std;#define MIN(a,b) (a<b ? a : b)int main ()
{int i, j;i = 100;j = 30;cout <<"较小的值为:" << MIN(i, j) << endl;return 0;
}
条件编译
有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。
#ifdef NULL#define NULL 0
#endif
#ifdef DEBUGcerr <<"Variable x = " << x << endl;
#endif
# 和 ## 运算符
# 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。
#include <iostream>
using namespace std;#define MKSTR( x ) #xint main ()
{cout << MKSTR(HELLO C++) << endl;return 0;
}
会出现这行结果:cout << MKSTR(HELLO C++) << endl;
变成了cout << "HELLO C++" << endl;
## 运算符用于连接两个令牌
#include <iostream>
using namespace std;#define concat(a, b) a ## b
int main()
{int xy = 100;cout << concat(x, y);return 0;
}
会出现这行结果:100;将cout << concat(x, y);
变成了100;
C++多线程
线程是程序中的轻量级执行单元,允许程序同时执行多个任务。
多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。
一般情况下,两种类型的多任务处理:基于进程和基于线程。
- 基于进程的多任务处理是程序的并发执行。
- 基于线程的多任务处理是同一程序的片段的并发执行。
并发 (Concurrency) 与并行 (Parallelism)
- 并发:多个任务在时间片段内交替执行,表现出同时进行的效果。
并行:多个任务在多个处理器或处理器核上同时执行
使用函数指针创建线程
#include <iostream>
#include <thread>void printMessage(int count) {for (int i = 0; i < count; ++i) {std::cout << "Hello from thread (function pointer)!\n";}
}int main() {std::thread t1(printMessage, 5); // 创建线程,传递函数指针和参数t1.join(); // 等待线程完成return 0;
}
线程管理
join()
join() 用于等待线程完成执行。如果不调用 join() 或 detach() 而直接销毁线程对象,会导致程序崩溃。
t.join();
detach()
detach() 将线程与主线程分离,线程在后台独立运行,主线程不再等待它。
t.detach();
线程传参
参数可以通过值传递给线程:
std::thread t(func, arg1, arg2);
线程同步与互斥
在多线程编程中,线程同步与互斥是两个非常重要的概念,它们用于控制多个线程对共享资源的访问,以避免数据竞争、死锁等问题。
1. 互斥量(Mutex)
互斥量是一种同步原语,用于防止多个线程同时访问共享资源。当一个线程需要访问共享资源时,它首先需要锁定(lock)互斥量。如果互斥量已经被其他线程锁定,那么请求锁定的线程将被阻塞,直到互斥量被解锁(unlock)。
#include <mutex>std::mutex mtx; // 全局互斥量void safeFunction() {mtx.lock(); // 请求锁定互斥量// 访问或修改共享资源mtx.unlock(); // 释放互斥量
}int main() {std::thread t1(safeFunction);std::thread t2(safeFunction);t1.join();t2.join();return 0;
}
2. 锁(Locks)
C++提供了多种锁类型,用于简化互斥量的使用和管理。
常见的锁类型包括:
- std::lock_guard:作用域锁,当构造时自动锁定互斥量,当析构时自动解锁。
- std::unique_lock:与std::lock_guard类似,但提供了更多的灵活性,例如可以转移所有权和手动解锁。
3. 条件变量(Condition Variable)
条件变量用于线程间的协调,允许一个或多个线程等待某个条件的发生。它通常与互斥量一起使用,以实现线程间的同步。
std::condition_variable用于实现线程间的等待和通知机制。
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void workerThread() {std::unique_lock<std::mutex> lk(mtx);cv.wait(lk, []{ return ready; }); // 等待条件// 当条件满足时执行工作
}void mainThread() {{std::lock_guard<std::mutex> lk(mtx);// 准备数据ready = true;} // 离开作用域时解锁cv.notify_one(); // 通知一个等待的线程
}
线程间通信
std::future 和 std::promise:实现线程间的值传递。
std::promise<int> p;
std::future<int> f = p.get_future();std::thread t([&p] {p.set_value(10); // 设置值,触发 future
});int result = f.get(); // 获取值