面向对象
title: C++面向对象基础 date: 2019-07-24 16:44:07 categories: - C++ tags: - C++ - 面向对象
访问控制限定符
- public:
谁都可以访问
- protected(默认):
只有自己和派生类可以访问
- private:
只有自己可以访问
类和结构体的区别
类有访问限定符,结构体没有
创建类,对象
class Dog {
string name;
public:
void eat();
};
// 栈区对象
Dog dog1;
Dog dog1[4];
// 堆区对象
Dog* dg1 = new Dog;
dg1->eat();
delete dg1;
dg1 = NULL;
Dog* dg2 = new Dog[3];
dg2[0].eat();
delete[] dg2;
dg2 = NULL;
string 类
- 查找
s1.find(查找的字符串,开始位置);
- 替换
s1.replace(开始替换位置,替换的位数,替换字符串);
s1 = "1234567";
s1.replace(s1.find("456"), 2, "abc"); // 123abc67
- 帮助文档, 选中要查找的关键字,按F1
构造析构
1. 构造
没有返回,函数名和类名相同,不需要显示调用,写在pulic里面
可以有多个重载的构造函数
``` cpp Dog* dg = new Dog("mm"); class Dog { string name; public: void eat();
Dog() {
cout << "无参构造" << endl;
}
Dog(string nm) {
name = nm;
cout << "有参构造" << endl;
}
Dog(string nm, int ag) :name(nm), age(ag) {
cout << "初始化表" << endl;
}
~Dog() {
cout << "析构" << endl;
}
};
Dog dog2; Dog dog1("dd"); Dog* dg = new Dog("mm"); Dog dog3("dy", 10); Dog dog4{ "dy", 10 }; ```
定义在头文件里定义,函数在cpp文件里定义
void Dog::eat() {
cout << name << endl;
}
2. 析构
析构函数没有参数
堆区变量在delete的时候调用, 栈区变量在作用域结尾调用
3. this指针
this指针用来联系成员函数和具体的事例化对象。
this就是指向当前对象地址的指针
4. 常成员
(1)常成员对象
修饰词const,只能初始化,不能赋值。 所以用初始化列表
class Dog {
const string name;
public:
Dog(string nm) :name(nm) {
}
初始化列表的顺序是按定义的顺序来排的,不是按照初始化列表的顺序。
(2)常成员函数
常量函数里不能改变成员变量的值,主要在编写的时候为了以防编写修改代码
void Dog::eat() const {
cout << name << endl;
}
const 关键词修饰前面的东西,如果前面的没有就修饰后面的
(3)常对象
在对象定义的起那面加 const 定义常对象
常对象只能调用常函数
mutable Dog dog1;
const Dog dog1;
** mutable 关键字修饰的对象可以在常函数里修改
拷贝构造
右键工程名,添加类。
函数定义一般在头文件里,函数实现在cpp文件里
Complex.h :
class Complex
{
private:
int m_real;
int m_vir;
public:
Complex(double real);
~Complex();
void print() const;
};
Complex.cpp :
Complex::Complex(double real)
{
m_real = real;
cout << "类型转换构造" << endl;
}
main.cpp :
Complex z = 1.2;
这里用的是类型转换构造(只有单参构造)
这一句不是直接复制,而是1.2先生成了一个Complex类然后再给z生成的。
若要取消这个功能,则要在构造函数定义前加explicit关键字
explicit Complex(double real);
拷贝构造
Complex::Complex(Complex& that) {
m_real = that.m_real;
m_vir = that.m_vir;
cout << "拷贝构造" << endl;
}
Complex z2 = z;
Complex* pz = new Complex(z2);
Complex* pz2 = pz; // 不是拷贝构造
拷贝构造函数会默认给
1. 浅拷贝
在拷贝的时候只是单纯复制指针,例子如下
class Ninja {
int* m_pAge;
public:
Ninja() {
cout << "默认构造" << endl;
}
explicit Ninja(int age) :m_pAge(NULL) {
if (age > 0) {
m_pAge = new int(age);
}
cout << "单参构造" << endl;
}
Ninja(const Ninja& that) {
m_pAge = that.m_pAge; // 单纯复制指针
}
~Ninja() {
if (m_pAge) {
delete m_pAge;
m_pAge = NULL;
}
cout << "析构" << endl;
}
void Introduce() const {
cout << *m_pAge << endl;
}
};
Ninja* kakaxi = new Ninja(20);
kakaxi->Introduce(); //20
Ninja* n1 = new Ninja(*kakaxi);
n1->Introduce(); //20
delete kakaxi;
n1->Introduce(); //乱码
编译器给的默认拷贝构造是浅拷贝
2. 深拷贝
深拷贝独立分配内存,不会出现浅拷贝的问题。
Ninja(const Ninja& that) {
//m_pAge = new int(*that.m_pAge); 或者这个
m_pAge = new int;
*m_pAge = *that.m_pAge;
}
友元
可以使外部函数访问内部private数据
1. 友元函数
class Point2D {
friend void print(const Point2D& point); //友元函数定义
friend class Point3D; //友元类
int m_x;
int m_y;
public:
Point2D(int x = 0, int y = 0) :m_x(x), m_y(y) {}
};
void print(const Point2D& point) {
cout << point.m_x << "," << point.m_y << endl;
}
2. 友元类
class Point3D {
Point2D m_p;
int m_z;
Point3D(int x = 0, int y = 0, int z = 0){
m_p.m_x = x; //使得可以访问Point2D的私有成员,需要定义友元类
m_p.m_y = y;
m_z = z;
}
};
注意:
要在每一个用到的类里面声明友元,不然会报错。类要提前声明好。
友元的定义是单向的,(Point2D不可访问Point3D的私有成员)
友元的定义不具有传递性
运算符重载
双目运算符重载
1. 友元函数重载
...
friend Complex operator + (const Complex& cp1, const Complex& cp2);
...
// cp1是左操作数,cp2是右操作数
Complex operator + (const Complex& cp1, const Complex& cp2) {
Complex tp;
tp.m_real = cp1.m_real + cp2.m_real;
tp.m_vir = cp1.m_vir + cp2.m_vir;
return tp;
}
2. 成员函数重载
Complex operator-(const Complex& c) {
Complex tp;
tp.m_real = this->m_real - c.m_real;
tp.m_vir = this->m_vir - c.m_vir;
return tp;
}
this做左操作数,所以只需要写一个参数
单目运算符重载
前++
Complex& Complex::operator++() {
this->m_real++;
this->m_vir++;
return *this;
}
<< 运算符只能用友元重载
ostream& operator<<(ostream& out,Complex& c) {
out << c.m_real << "+" << c.m_vir << "i";
return out;
}
还有>>
istream& operator>>(istream& in, Complex& c) {
in >> c.m_real >> c.m_vir;
return in;
}
注意在.h文件里也要加上需要的头文件
后++, 需要用一个哑元来区分(规定这样)
Complex Complex::operator ++ (int) {
Complex tp = *this;
this->m_real++;
this->m_vir++;
return tp;
}
三目运算符不能重载
=,(),[],->,->* 必须是成员函数
双目运算符建议友元重载,单目运算符建议成员重载
拷贝赋值
由于存在浅拷贝的问题,所以需要自己实现拷贝函数
由于需要作左值,所以返回值要是个地址
- 避免自赋值
- 分配新资源
- 拷贝新内容
- 释放旧资源
- 返回自引用
cArray& operator = (const cArray& that) {
cout << "拷贝复制构造" << endl;
if (&that != this) *this;
int *ptp = new int[that.m_size];
memcpy(ptp, that.m_array, that.m_size*sizeof(that.m_array[0]));
if (m_array) {
delete[] this->m_array;
m_array = NULL;
}
swap(ptp, this->m_array);
delete[] ptp;
ptp = NULL;
return *this;
}
拷贝赋值尽可能赋值构造析构的代码
cArray& operator = (const cArray& that) {
cout << "拷贝复制构造" << endl;
if (&that != this) *this;
cArray temp(that);
swap(this->m_array, temp.m_array);
return *this;
}
静态成员
静态成员属于类,不属于对象。声明周期进程级。(全局的)
相当于全局变量,知识多了一个类的作用域和访问权限
静态成员变量
静态成员变量定义只能在类外面,不能在构造函数初始化
static double m_rate; //类里
double Account::m_rate = 0.2; //类外
静态成员函数
static void adjust(double rate) {
m_rate = rate; //没有this指针
}
Account::adjust(0.3);
单例
只能实例化一个对象
饿汉模式:程序启动就创建
class Single {
int m_data;
private:
Single(int data):m_data(data){}
Single(const Single&){}
static Single s_instance;
public:
static Single& getInstance() {
return s_instance;
}
};
Single Single::s_instance(100);
Single& s1 = Single::getInstance(); // s1和s2是一个对象
Single& s2 = Single::getInstance();
懒汉模式:用的时候再创建
class Singleton {
int m_data;
private:
Singleton(int data) :m_data(data){}
static Singleton* s_instance;
public:
static Singleton& getInstance(int data=0) {
if (!s_instance){
s_instance = new Singleton(data);
}
return *s_instance;
}
static void clear() {
if (!s_instance) {
delete s_instance;
s_instance = NULL;
}
}
};
Singleton* Singleton::s_instance = NULL;
成员指针
成员变量再对象中的相对地址。指向成员变量的指针是成员指针
int Integer::*pvalue = &Integer::m_i;
i1.*pvalue = 50;
成员指针是偏移量
class A{
public:
int m_a;
int m_b;
A(int a = 10, int b = 20) :m_a(a), m_b(b){}
};
A a;
int A::*p = NULL;
cout << *(int*)&p << endl; //-1
p = &A::m_a;
cout << *(int*)&p << endl; //0
p = &A::m_b;
cout << *(int*)&p << endl; //4
去常
int i = 4;
const int *pc1 = &i; // 不能通过指针修改i
int *p1 = const_cast<int*>(pc1); //去常
可以 改int const 变量的值,因为int const 声明的变量不是在常量区的,变量不能改,但是里面的值是可以改动的
int const i = 5;
int const *p = &i;
int *k = const_cast<int*>(p);
*k = 10;
cout << i << endl << *k << endl << *p << endl; //5 10 10
因为它是从寄存器里读取值,那个时候寄存器还是原来的i值,所以是5
用volatile告诉编译器它是易变的
volatile int const i = 5;
volatile int const *p = &i;
int *k = const_cast<int*>(p);
*k = 10;
cout << i << endl << *k << endl << *p << endl; // 10 10 10
继承
基类(父类)和派生类(子类)。共性和个性
public 公有继承
不改变父类的属性
子类存在但不能访问父类的私有成员,可以访问受保护和共有成员
同名隐藏:在子类若有和父类同名的函数,那么调用子类的函数
class Human {
int m_age;
string m_name;
public:
Human(int age = 20, string name = "fy") :m_age(age), m_name(name){
cout << "父类构造" << endl;
}
void eat() {
cout << "eat" << endl;
}
void sleep() {
cout << "sleep" << endl;
}
};
class Teacher :public Human { //公有继承
public:
Teacher() {
cout << "teacher构造" << endl;
}
void teach() {
cout << "teach" << endl;
}
void eat() { //同名隐藏,eat调用子类的函数
cout << "T_eat" << endl;
}
};
截然性:子类对象可以在任何时候看成基类。
Teacher b;
Human *p1; //用父类的指针指向子类
p1 = &b;
因为构造子类的时候会先构造基类,所有有个基类子对象,地址和子类相同。
这样操作减少了访问范围。
protected 保护继承
基类的public成员变成protected成员
private 私有继承
基类的所有成员都变成private成员
改变成员属性,不能改变父类的private
class Teacher :public Human { //给了所有权限
protected:
using Human::eat; // 改变public 变成 protected
public:
Teacher() {
cout << "teacher构造" << endl;
}
void teach() {
cout << "teach" << endl;
}
void eat() { //同名隐藏
cout << "T_eat" << endl;
}
};
阻断继承
把构造函数写成私有成员,就不能继续继承了
class A{};
class B :public A{};
class C :public B{
C(){}
};
class D :public c{}; // 编译错误
另外,父类的构造不能被子类继承
在继承的时候会调用父类默认构造函数
若需要调用别的构造函数,则需要显示调用,如下
class A{
int m_data;
public:
A(){
cout << "默认构造" << endl;
}
A(int a) :m_data(a){
cout << "单参构造" << endl;
}
};
class B :public A{
int m_b;
public:
B(int x) :A(10), m_b(x) {
cout << "B的构造" << endl;
}
};
多重继承
一个子类继承了多个父类
在分配内存的时候,从左到右分配内存
class Telephone{
public:
Telephone() {
cout << "Telephone构造" << endl;
}
void call() {
cout << "call" << endl;
}
};
class Camera {
public:
Camera() {
cout << "Camera构造" << endl;
}
void takephoto() {
cout << "photo" << endl;
}
};
class IphoneXMax : public Telephone, public Camera {
public:
IphoneXMax() {
cout << "iphone构造" << endl;
}
};
此时,用截然性,用不同的父类指针指向子类会得到不同的地址
Telephone *p1 = &ip;
Camera *p2 = &ip;
因为子类会隐式转换成基类,从而实现截然性
菱形继承
X和Y继承A,B继承X和Y
class A{
public:
A() {
cout << 'A' << endl;
}
void func() {
cout << "func" << endl;
}
};
class X :public A{
public:
X() {
cout << "x" << endl;
}
};
class Y :public A {
public:
Y() {
cout << "Y" << endl;
}
};
class B : public X, public Y{
public:
B() {
cout << "b" << endl;
}
};
B b;
b.func(); //报错,func的调用不明确
b.X::func(); //需要这么写才正确
虚继承
class X :virtual public A{
public:
X() {
cout << "x" << endl;
}
};
class Y :virtual public A {
public:
Y() {
cout << "Y" << endl;
}
};
B b;
b.func();
这样就可以避免重复生产A,导致调用的不明确
虚继承会产生一个共用的A
并且虚继承的类会有一个指针指向虚基表(一个数组),里面储存的是A对象的偏移值
一个小问题
class Animal {
public:
void say() {
cout << "?DF??ds" << endl;
}
};
class Dog :public Animal{
public:
void say() {
cout << "汪汪汪" << endl;
}
};
Animal *p = new Animal();
p->say(); // ?DF??ds
delete p;
p = new Dog();
p->say(); // ?DF??ds
delete p;
p = NULL;
因为截然性,所以Animal 的指针可以指向 Dog
但是它不能用Dog的成员函数
要用多态来解决这个问题
多态
虚函数
把基类的say()声明成虚函数,之后狗就会汪汪叫,符合了人类的逻辑
class Animal {
public:
virtual void say() {
cout << "?DF??ds" << endl;
}
};
- 当子类的函数形式(返回类型,函数名,参数表)和父类的虚函数一样的时候,子类的该函数也会默认成为虚函数(无论加不加virtual)
此时,子类的虚函数会覆盖(重写)父类的虚函数(和隐藏不同)
- 只有成员函数(非静态)可以覆盖
使用虚函数后,基类指针会根据实际指向类型来分别调用不同的函数
若不适用,基类指针会根据指针的类型来调用函数
这个现象称为多态
多态函数
单态函数:一个函数对应一个功能
多态函数:一个函数多个功能
int add(int a, int b) {
return a + b;
}
int mul(int a, int b) {
return a*b;
}
int divi(int x, int y) {
return x / y;
}
int calc(int x, int y, int(*fun)(int, int)) {
return fun(x, y);
}
cout << calc(1, 2, add) << endl;
cout << calc(1, 2, mul) << endl;
cout << calc(1, 4, divi) << endl;
虚析构
父类指针指向子类的时候,要用虚析构,避免子类个性成员内存泄漏
class A{
public:
A() {
cout << "构造" << endl;
}
virtual ~A() { // 若不加,那么B会少个析构
cout << "析构" << endl;
}
};
class B :public A{
int* p;
public:
B() {
cout << "B构造" << endl;
p = new int(10);
}
~B() {
cout << "B析构" << endl;
delete p;
p = NULL;
}
};
A* a = new B;
delete a;
纯虚函数和抽象类
virtual void foo(int) = 0; //纯虚函数/抽象方法
若类有一个纯虚函数,那么这个类是抽象类
抽象类不能实例化对象
若一个类所有的函数都是纯虚函数,那么这个类是纯抽象类(接口类)
纯抽象类只用来提供接口。
若一个类太抽象,一般写成接口类
在继承的时候子类必须对基类形成完全覆盖,否则也会称为抽象类,而不能实例化对象。
IO流
C++是一个单独的语言,有单独的语法。C++兼容C,但是比C功能强大
用C的输出不能满足C++的需求
流格式化
头文件
切换输出格式
int a = 21;
cout.setf(ios::showbase); //为整数加一个进制前缀
cout << "dec " << a << endl;
cout.unsetf(ios::dec); //取消10进制格式
cout.setf(ios::hex); // 设置16进制格式
cout << "hex: " << a << endl;
cout.unsetf(ios::hex);
cout.setf(ios::oct);
cout << "oct: " << a << endl;
const char *pt = "sirius";
//直接利用格式成员函数
cout.width(10); //指定字符域宽, 一次
cout << pt << endl; // " sirius"
cout.width(10);
cout.fill('*');
cout << pt << endl;
double pi = 22.0 / 7;
cout << pi << endl;
cout.setf(ios::scientific); //科学计数法
cout << pi << endl;
cout.unsetf(ios::scientific);
//四个字符宽度 右对齐 用'0'填充
cout << setw(4) << right << setfill('0') << 1 << endl;
cout << showbase << oct << 21 << endl; //八进制输出
bool b = false;
cout << b << endl;
cout << boolalpha << b << endl; // 布尔用字母输出,开关
cout << noboolalpha << b << endl;
cout ,cerr 和 clog
cout 标准输出对象(有缓冲区), 可以重定向到一个文件里
cerr 标准错误流(无缓冲区),必须显示在显示器上
clog 标准错误流 (有缓冲区)
标准输入输出流
cout << "a";
cout << "b" << ends; // \0
cout << "c" << endl;// \n + flush
cout << "d" << flush; //刷新缓冲区
//获取单个字符
char c = cin.get();// 输单个字符后换行
cout << c << endl;
cout.put(c);
endl(cout << "hello");
cout << "world" << endl;
//获取行
char str[200] = { 0, };
cin.getline(str,20,'/'); //读取最大20个,遇到'/'就停止
cout << str;
字符串流
头文件
把字符串转换成数字
string res = "100";
int num = 0;
stringstream sst;
sst << res;
sst >> num;
cout << num << endl;
分割提取字符+类型转换
string ip = "192.168.0.1";
stringstream sst2(ip);
int a1, a2, a3, a4;
char ch;
sst2 >> a1 >> ch >> a2 >> ch >> a3 >> a4;
拼接字符串+类型转换
stringstream sst3;
int num = 3;
char str[] = "14159";
sst3 << num << ch << str;
cout << sst3.str() << endl;
文件流
头文件
读写文件
struct Student {
char name[20];
int num;
int age;
char sex;
};
Student stu[3] =
{ "a", 1001, 18, 'm', "Really", 1002, 24, 'f', "dd", 1003, 19, 'm' };
//写入文件(二进制)
ofstream outfile("stu.dat", ios::binary);
if (!outfile) {
cerr << "open error!" << endl;
abort();
}
for (int i = 0; i < 3; i++) {
outfile.write((char*)&stu[i], sizeof(stu[i]));
}
outfile.close();
//读取文件
Student rStu[3];
ifstream infile("stu.dat", ios::binary);
if (!infile) {
cerr << "open error!" << endl;
abort();
}
for (int i = 0; i < 3; ++i) {
infile.read((char*)&rStu[i], sizeof(rStu[i]));
}
infile.close();
//测试
for (int i = 0; i < 3; ++i) {
cout << "name:" << rStu[i].name << endl;
cout << "nu:" << rStu[i].num << endl;
cout << "age:" << rStu[i].age << endl;
cout << "sex:" << rStu[i].sex << endl;
}
把三个文件合起来,又分开
注意要在文件之前储存信息,方便分离
struct FileInfo {
char filename[20];
int filesize;
};
void pack() {
FileInfo fileList[3] = {
{ "1.png", 0 },
{ "2.png", 0 },
{ "3.png", 0 } };
fstream file[3];
for (int i = 0; i < 3; ++i) {
file[i].open(fileList[i].filename, ios::in | ios::binary);
file[i].seekg(0, ios::end);
fileList[i].filesize = file[i].tellp();
file[i].seekg(0, ios::beg);
}
fstream newfile("backups.dat", ios::out | ios::binary);
newfile.write((char*)fileList, sizeof(fileList));
char *tmp = new char[800000];
for (int i = 0; i < 3; ++i) {
file[i].read(tmp, fileList[i].filesize);
newfile.write(tmp, fileList[i].filesize);
}
delete tmp;
tmp = NULL;
for (int i = 0; i < 3; ++i) file[i].close();
newfile.close();
}
void unpack() {
FileInfo pic[3];
fstream file("backups.dat", ios::in | ios::binary);
file.read((char*)pic, sizeof(pic));
char *p = new char[800000];
for (int i = 0; i < 3; ++i) {
file.read(p, pic[i].filesize);
char tp[20] = "out_";
strcat(tp, pic[i].filename);
fstream outfile(tp ,ios::out | ios::binary);
outfile.write(p, pic[i].filesize);
outfile.close();
}
delete p;
p = NULL;
file.close();
}
异常
- 利用返回值的不同来判断
缺点:需要逐层判断
- setjmp 和longjmp
#include <setjmp.h>
jmp_buf j_err;
void func3() {
FILE* pFile;
if (!(pFile = fopen("null", "r"))) {
cout << "调用longjmp\n";
longjmp(j_err, -1);
cout << "调用longjmp后" << endl;
}
return;
}
void func4() {
cout << "调用func3前" << endl;
func3();
cout << "调用func3前" << endl;
}
if (setjmp(j_err) == 0) {
cout << "第一次调用setjmp" << endl;
func4();
}
else{
cout << "第二次调用setjmp\n";
}
/***
*第一次调用setjmp
调用func3前
调用longjmp
第二次调用setjmp
*/
- 抛出异常
throw 抛出异常
try { } catch (参数) { }
只能捕获一个异常,出现异常后马上就会捕获
void func3() {
FILE* pFile;
if (!(pFile = fopen("null", "r"))) {
throw - 1;
}
char *pb = (char*)malloc(0xfeeefeee);
if (!pb) {
throw "内存申请失败";
}
return;
}
try {
func3();
}
catch (int ex) {
if (-1 == ex) {
cout << "文件打开失败" << endl;
}
}
catch (const char* ex) {
cout << ex << endl;
}
捕获异常的顺序从上往下匹配
标准库异常
overflow_error 是一个标准库的异常类
exception是它的基类
若不知道异常的种类,可以写exception
其他的异常可以自己写个类,继承exception类
void push(int data) {
throw overflow_error("堆栈上溢");
}
try {
push(10);
}
catch (exception &ex) {
cout << ex.what() << endl;
}
继承标准库异常类
class Overflow: public exception
{
public:
const char* what() const throw() {
return "堆栈撑不住了";
}
};
void push(int data) {
throw Overflow();
}
RTTI
static_cast<目标类型>(原类型变量)
- 隐式类型转换的逆转换
double adouble = 11.11;
void *pv = static_cast<void *>(&adouble);
double *pd = static_cast<double *>(pv); // 要加static_cast才可以转
- 动态类型转换
class A {
public:
virtual void foo() {
cout << "A::foo()" << endl;
}
};
class B :public A {
public:
void foo() {
cout << "B::foo()" << endl;
}
};
B b;
A *pa = &b; //上行转换 安全
A a;
B *pb = static_cast<B*>(&a); // 下行转换,不安全
cout << typeid(pa).name() << endl; // 运行时类型识别的函数 class A
cout << typeid(pb).name() << endl; // class B
B *pb1 = dynamic_cast<B*>(&a); // 在有虚函数的前提下,动态类型转换 安全
// dynamic_cast 会引发动态类型识别, 使用typeid比较声明类型和实际类型
// 对于指针 不一致的返回null 对于引用的比较,不一致的抛出bad_typeid异常
把父类转子类要用dynamic_cast<>()转换
C++11
新增类型
long long 8字节
unsigned long long 8字节
char16_t 2字节
char32_t 4字节
nullptr 空指针
int *p = nullptr;
类型别名
可以用于给长名字取别名
using dtype = int;
dtype dt = 20;
auto
auto ai = 10;
auto as = "Hello";
cout << typeid(as).name() << endl;
初始化
class A {
public:
int m_a{ 10 };
};
int num{ 10 };
int arr[]{1, 2, 3, 4, 5};
范围for循环
for (auto i : arr) {
cout << i << " ";
} // 1 2 3 4 5
返回类型后置
auto add(int a, int b)->int {
return a + b;
}
默认函数和已删除函数
class A {
public:
A() = default;
A(const A& that) = delete;
};
委托构造函数
class B {
int m_a;
int m_b;
int m_c;
public:
B(int a, int b, int c) :m_a(a), m_b(b), m_c(c){}
B() :B(10, 20, 30){} // 无参构造后面调用三参构造
B(int a) :B(a, 20, 30){}
};
右值引用
int num = 10;
int &lnum = num; // 左值引用
int &&rnum = 10; // 右值引用
//用处 移动语义 移动构造
lamda表达式(匿名函数)
auto sum = [](int a, int b){return a + b; };
cout << sum(1, 3) << endl;
override和final
override确保成功覆盖
类被final修饰不能被继承
class A {
public:
virtual void foo() {
cout << "A::foo()" << endl;
}
virtual void bar() final{}
};
class B :public A {
public:
void foo() override {
cout << "B::foo()" << endl;
}
};
函数模板
泛型
可以把类型作为参数传进去
template<typename T> // 模板头
T max(T a, T b) {
return a > b ? a : b;
}
cout << max<int>(3, 4) << endl;
cout << max(3, 4) << endl; // 隐式推断类型
template<typename T,typename A> // 2各以上的模板头
类模板
template<typename T>
class Stack {
list<T> m_list;
public:
Stack() {
m_list.clear();
}
~Stack() {
m_list.clear();
}
Stack(Stack<T> const& that) :m_list(that.m_list){}
Stack <T>& operator=(Stack<T> const& that) {
if (&that != this) m_list = that.m_list;
return *this;
}
void push(T const& data);
void pop();
T& top();
T const& top() const;
bool empty() const;
};
//类外定义要带上模板头
template<typename T>
void Stack<T>::push(T const& data) {
m_list.push_back(data);
}
template<typename T>
void Stack<T>::pop() {
if (empty()) {
throw underflow_error("堆栈下溢");
}
m_list.pop_back();
}
template<typename T>
T& Stack<T>::top() {
if (empty()) {
throw underflow_error("堆栈下溢");
}
return m_list.back();
}
template<typename T>
T const& Stack<T>::top() const {
return const_cast<Stack<T>*>(this)->top(); // this指针有常属性,要去常
}
template<typename T>
bool Stack<T>::empty() const {
return m_list.empty();
}
// main
try {
Stack<int> s1;
for (int i = 0; i < 10; ++i) {
s1.push(i);
}
while (!s1.empty()) {
cout << setw(2) << left << s1.top();
s1.pop(); // 9 8 7 6 5 4 3 2 1 0
}
cout << endl;
}
catch (exception &ex) {
cout << ex.what() << endl;
getchar();
return -1;
}
类模板是二次编译,所以声明和定义要写在同一个文件里,建议在头文件里
friend ostream& operator << <T>(ostream& out, Stack<T>& stack);
友元声明的时候要加泛型支持,在名字后面加
模板类继承
父类自定义了构造函数,子类必须要用初始化列表初始化
继承的时候,如果子类不是模板类,必须指明父类的类型
如果子类是模板类,要么指明父类的类型,要么用子类的泛型来指定父类
模板特化
函数模板的完全特化
template <typename T>
T max(const T a, const T b) {
return a > b ? a : b;
}
template <typename T>
const char* max(const char* a, const char* b) {
return strcmp(a, b) > 0 ? a : b;
}
//这样比较字符串就不会比较指针了
类模板的完全特化
class Stack<bool> {
//...
}
类模板的偏特化
template <typename T,typename Allocator>
class vector {
//...
}
template <typename Allocator>
class vector<bool,Allocator> {
//...
}
C++17
安装
安装 在这里下载 https://nuwen.net/mingw.html

安装完后添加环境变量即可

如果有变量冲突的话,可以用 where gcc 查看
编译
g++ [file.cpp] --std=c++17 -o file
for
auto [v, len] = G[u][k-1]; // pair
初始化
vector<vector<pair<int, int>>> G(n+1, vector<pair<int,int>>());
vector<vector<vector<int>>> dp(2, vector<vector<int>>(n+1, vector<int>(m+1, -oo)));
函数
function<int(int,int)> dfs = [&](int x, int fa) -> int {
d[x] = 0;
for (auto [v, len] : G[x]) if (v != fa) {
int td = dfs(v, x);
if (td + len > d[x]) {
d[x] = td + len;
}
}
return d[x];
};