cpp-learn-basic

利用汇编挖掘编程语言的本质

误区:不要相信非一手中文资料

建议:

  • 官方文档最重要。 英文资料 > 中文资料
  • 验证知识点正确性
  • 掌握汇编语言是验证知识点正确性的方式。

汇编语言 与 机器语言一一对应,但是汇编语言与高级语言不一定一一对应。

基本语法

常量

  1. 宏常量:

    #define 常量名 常量值

    通常在文件上方定义.

  2. const修饰的变量

    const 数据类型 常量名 = 常量值

    通常在变量定义前加关键字const,修饰该变量为常量,不可以修改.

数据类型

数据类型的意义:给变量分配合适的内存空间

整型
  • short:2字节

  • int:4字节

  • long:windows 4字节 ,linux(32)4字节,(64)8字节

  • long long:8字节

  • sizeof 是关键字 不是函数 :sizeof(数据类型 / 变量)

浮点型
  • float:4字节,7位有效数字
  • double:8字节,15 16位有效数字
字符型

char ch = 'a'

  • C 和 C++只占用一个字节
  • 字符型存储的是对应的ASCII码
  • 转义字符
    用于不能显示出来的ASCII字符
字符串类型

表示一串字符

  1. C风格的字符串:char 变量名[] - "字符串值";
  2. C++风格字符串:string 变量名 = "字符串值", 引入头文件 <string>
布尔类型

只占用一个字节

  • true:本质是0意外的任何数
  • false:本质是0
数据的输入

关键字:cin

语法:cin >>

数组

数组特点:

  • 放在一块连续的内存空间中
  • 数组中的每个元素都是相同的数据类型
一维数组

定义:

  1. 数据类型 数组名[数组长度];
  2. 数据类型 数组名[数组长度] = {v1, v2,...} ;
  3. 数据类型 数组名[] = {v1, v2 ,,,};

一维数组的数组名的用途:

  1. 可以统计整个数组在内存中的长度sizeof(数组名)
  2. 可以获取数组在内存中的首地址:直接用数组名arr或者 数组第一个元素的地址&arr[0]

数组元素互换

二维数组

定义:

  1. 数据类型 数组名[行数][列数];
  2. 数据类型 数组名[行数][列数] = {{v11,v12,,} , {v21,v22,,,}, ,,,}
  3. 数据类型 数组名[行数][列数] = {v11,v12,, , v21,v22,,,, ,,,}
  4. 数据类型 数组名[][] = {v11,v12,, , v21,v22,,,, ,,,}

数组名称的用途

  1. 查看所占空间大小
  2. 获取二维数组的首地址: = 第一行元素的首地址 = 第一行第一个元素的首地址

函数

值传递:形参无论发生什么变化都不影响实参。

**函数的申明:**申明可以写多次

**函数的定义:**定义只能有一次

函数的分文件编写

  1. 创建头文件 .h
  2. 创建源文件 .cpp
  3. 在头文件中写函数的申明
  4. 在源文件中写函数的定义,源文件要include 头文件

指针

通过指针保存地址, 指针就是一个地址

定义指针:
  1. 定义的时候不赋值
  2. 定义的时候赋值
使用指针:

指针占的数据空间:

32位 4字节,64位8字节。

空指针NULL
  1. 给指针变量初始化
  2. 空指针不可以进行访问。(0~255之间的内存编号是系统占用的,不可以访问)
野指针

空指针和野指针都不是申请的地址,尽量避免

const 修饰指针
  1. 常量指针:const 数据类型 * 名称
    指针的指向可以改,但是指针指向的值不可以改

  2. 指针常量:数据类型 * const 名称

    指针的指向不可以改,指针指向的值可以改

  3. const 既修饰指针又修饰常量:const 数据类型 * const 名称

结构体

相关概念

用户自定义数据类型

申明struct 结构体名 {结构体成员列表}

创建

  1. 先创建,再赋值(使用.访问结构体成员)

  2. 创建的时候直接赋值
    struct 结构体名 变量名字 = {结构体成员列表的值}

  3. 申明的时候创建一个结构体变量,然后再赋值

    struct 结构体名 { 结构体成员列表的值 } 结构体变量名字;
    结构体变量名字.v1 = v1 ....

使用:使用 .访问结构体元素

结构体数组
结构体指针

操作符->通过结构体指针访问结构体指针的元素

结构体嵌套结构体
// 注意嵌套 和 结构体的书写顺序
struct Subject {
	string name;
	int score;
};

struct Teacher {
	string name;
	int id;
};

struct Student {
	string name;
	int id;
	string password;
	// 考试科目 语数外 + 六选三
	//Subject subjects[6];
	struct Teacher teacher;
};
int main() {
	Teacher teacher = { "luoxiang", 1 };

	Student s = { "zhangsan", 1, "123456" ,teacher};

	Student* ps = &s;
	cout << s.name << " " << ps->name << endl;
	cout << s.id << " " << ps->id << endl;
	cout << s.password << " " << ps->password << endl;
	cout << s.teacher.name << " " << ps->teacher.name << endl;
	cout << s.teacher.id << " " << ps->teacher.id << endl;

}
结构体作为函数参数
  • 值传递
  • 地址传递:能够改变实参的值,这样的话不会创建副本,可以减少内存空间
结构体中使用const

作用:用const来防止误操作

使用场景:函数的参数用指针,指针前面加 const。
指针指向的值不可以发生改变

核心编程

内存分区模型

代码区

存放函数体的二进制代码,由操作系统管理

共享 只读

运行前才有

全局区

存放全局变量静态变量常量(包括字符串常量 和 const 修饰的全局常量)。程序结束后,由操作系统释放

运行前才有

栈区

存放函数的参数值 、 局部变量。由编译器自动分配 释放。

运行后才有

注意事项:函数不要返回函数里面局部变量的地址(第一次是可以用局部变量的地址,是因为编译器做了保留操作)

堆区

由程序员分配、管理 和 释放。若程序员没有处理,程序结束时自动回收。

运行后才有

用关键字new在堆区开辟内存,new返回的这个数据类型的指针

int* pa = new int(10); // 10是int型的初始值。 new 返回的是一个地址,用一个指针去接受。这个局部变量存放到堆区,但是这个指针仍然在栈区

delete pa;

用关键字delete释放内存

new int(val)对应的是 delete

new int[cnt]对应的是 delete[]

int* pa = new int[10]; // 在堆区开辟一个10个元素的数组

delete[] pa; // 释放这个数组

bool* array = new bool[10];   //未初始化
bool* array = new bool[10](); //初始化为0

引用

作用:给变量取别名

语法数据类型 &别名 = 原名

操作别名 跟 操作原名是一样的。

引用的注意事项
  1. 引用必须要初始化
  2. 引用在初始化厚不能改变
int a = 10;
int &b = a;
引用作为函数参数

作用:函数传参的时候,可以利用引用的技术让形参修饰实参

有点:可以简化指针修改实参

引用作函数的返回值

注意

  1. 不要返回局部变量的引用
  2. 函数的调用可以作为左值
int& refc_test() {
	static int aa = 10;
	return aa;
}
void main() {
    int& aa = refc_test();
	cout << aa << endl;
	cout << aa << endl;
   	// refc 是static变量aa的别名,下面这一行的操作是改变aa的值
	refc_test() = 100; 
	cout << aa << endl;
	cout << aa << endl;
}
引用的本质

本质:在C++内部实现就是一个指针常量

推荐使用,本质上也是指针。

常量引用

作用:常量引用通常用来修饰形参,防止误操作

在函数列表中,可以加入const修饰形参,防止形参改变实参

// 加上const厚,编译器将代码修改 int temp = 10;const int & ref = temp;
const int & ref = 10;

函数(高阶)

函数的默认参数
int func(int a, int b = 1, int c = 2) { ... } // b ,c 有默认值 1 ,2 

注意事项:

  1. 如果某个位置有默认参数,那这个位置右边的参数都要有默认参数
  2. 声明和实现只能有一个有默认参数
函数的占位参数

语法:返回值类型 函数名(数据类型) {}

// 重载++后置运算符的
complex operator++(int);

形参列表中有占位参数,调用的时候就必须填补该参数

占位参数还可以有默认参数

函数重载

**作用:**函数名可以相同,提高复用性。

重载满足条件:

  • 同一个作用域下
  • 函数名相同
  • 函数参数类型不同 、个数不同 或者顺序不同

函数的返回值不能满足重载的条件

注意事项:

  1. 引用可以作为重载条件

    void func(int& a) { // int &a =  aaa;
    	cout << "函数输出 (int& a) " << endl;
    }
    
    void func(const int& a) { //  const int &temp = 10;
    	cout << "函数输出 (const int& a) " << endl;
    }
    
    void main() {
       	int aaa = 10;
    	func(aaa);
    	func(10);
    }
    
  2. 重载遇到默认参数:容易导致歧义

    // 会有歧义
    void func(int a, int b = 1){}
    void func(int a){} 
    
  3. 函数重载的实质:编译器会将函数名和参数类型[包括个数和顺序]一起组合成一个新的函数名字进行区分和调用[name mangling 或者 name decoration 技术]。

类和对象

访问权限
  1. public:类 内外都可以访问
  2. protected:类内可以访问,内外不可以。子类可以访问父类
  3. private:类内可以,类外不可以
struct 和 class的区别

两者的唯一区别:默认的访问权限不同。

  • struct 的默认权限为公共
  • class 的默认权限为私有
对象的初始化和清理

构造函数,析构函数(编译器自动调用,必须要写,编译器自动写的是空的)

构造函数语法类名() {}

  • 不需要返回值,不需要void
  • 函数名称与类名相同
  • 构造函数可以有参数,因此也可以发生重载
  • 会被自动调用,只调用一次

析构函数语法~类名() {}

  • 不需要返回值,不需要void
  • 函数名称与类名相同,在前面加~
  • 构造函数不可以有参数,不可以发生重载
  • 会被自动调用,只调用一次
构造函数的分类和调用

分类:

  • 有参和无参

  • 普通构造和拷贝构造

    class Person {
        public:
        Person(const Person & p) {
            // 这是拷贝构造函数
        }
    }
    

调用方式:

  • 括号法

    Person p1; // 调用的是默认无参构造函数
    Person p2(参数); // 调用有参构造
    Person p3(Person实例); // 调用拷贝构造函数
    
  • 显示法

    Person p1;// 调用默认构造
    Person p2 = Person(10); // 调用有参构造
    Person p3 = Person(p2);// 调用拷贝构造
    
  • 隐式转换法

    Person p4 = 10;// 相当于写了Person p4 = Person(10) 有参构造
    Person p5 = p4; // 拷贝构造
    

    注意事项:

    1. 匿名对象:Person(10),当前行执行结束的时候,系统会立即回收匿名对象
    2. 不要利用拷贝函数初始化匿名对象
拷贝构造函数调用时机
  • 使用一个创建完毕的对象来初始一个新对象

  • 值传递的方式给函数参数传值

    void do_work(Person p) {}
    main() {
        Person p;
        do_work(p);
    }
    
  • 以值的方式返回局部对象

    Person do_work(){
        Person p;
        return p;
    }
    main() {
        Person p = do_work();
    }
    
构造函数的调用规则

默认清空下,C++编译器至少会给一个类3个构造函数

  1. 默认构造函数
  2. 默认析构函数
  3. 默认拷贝函数

调用规则如下:

  1. 如果有自定义有参构造函数,c++不会提供无参构造函数
  2. 如果有自定义拷贝构造函数,c++不会提供拷贝构造函数
深拷贝 和 浅拷贝

浅拷贝:简单的赋值拷贝操作。

深拷贝:在堆区申请新的空间,尽心拷贝操作。

浅拷贝的问题:堆区的内存会重复释放

#pragma once

#ifndef PERSON_H_
#define PERSON_H_

#include <iostream>

class Person {
private:
	int age;
	int* pHeight;

public:
	Person(int age, int height);
	int getAge();
	int getHeight();
	~Person();
};

#endif
#include "Person.h"


Person::Person(int age, int height) {
	Person::age = age;
	// 在堆区开辟一个数据,用一个指针去这个数据
	Person::pHeight = new int(height);
}

int Person::getHeight() {
	return *pHeight;
}
int Person::getAge() {
	return age;
}

Person::~Person() {
	// 将堆区开辟的数据释放
	if (pHeight != NULL) {
		delete pHeight;
		pHeight = NULL;
	}
}

以上代码会导致问题,拷贝构造函数必须要深拷贝

	Person(const Person& p);
Person::Person(const Person& p) {
    // 深拷贝之前 先做 安全性检查
    if(this->pHeight != NULL) {
        delete this->pHeight;         // 释放空间
        pHeight = NULL; // 空指针
    }
	Person::age = p.age;
	// 申请新的空间 进行拷贝
	Person::pHeight = new int(*p.pHeight);
}
初始化列表给对象赋值
#pragma once

#ifndef USER_H_
#define USER_H_
class User {
private:
	int _a;
	int _b;
	int _c;
public:
	User(int a, int b, int c);
	~User();
};
#endif
#include "User.h"
// 初始化列表
User::User(int a, int b, int c) :_a(a), _b(b), _c(c) {}
User::~User() {}
类对象作为类成员

结论:当其他类对象作为本类成员,构造的时候先构造类对象,再构造自身。析构的顺序是相反的

静态成员变量 静态成员函数
  • 静态成员变量:
    • 所有对象共享同一份数据(可以改值)
    • 在编译阶段分配内存
    • 类内声明,类外初始化
    • 可以通过对象进行访问p.m_a;也可以通过类名进行访问 Person::m_a
    • 也可以有访问权限
class Person {
    // 类内声明
    static int m_a;
}
// 类外初始化
int Person::m_a = 100;
  • 静态成员函数
    • 所有对象共享一个函数
    • 静态成员函数只能访问静态成员变量
    • 可以通过对象访问,也可以通过类名进行访问。

成员变量和成员函数是分开存储的:只有非静态成员 才属于类的对象上

class Person {
    int m_A; //非静态成员 属于类的对象上
    static int m_B; // 静态成员变量 不属于类的对象上
    void  func() {} // 非静态成员函数 不属于类的对象
    static void fu() {} //静态成员函数 不属于类的对象
}
int Person::m_B = 100;
void main() {
    Person p;
    cout << sizeof(p) << endl; // 输出 4 个字节(存储的是m_A  4个字节)。当这个类是空的 对象是空的,C++编译器存储的大小为1字节。
}
this指针

this指针指向被调用的成员函数所属的对象。谁调用就指向谁

用途:

  • 当函数的形参和成员变量同名的时候,用```this->``区分
  • 大概类的非静态成员你函数中返回对象本身,使用 return *this; **函数返回值为 引用类型 **Person&,如果没有这样做,则会调用拷贝构造函数,创建一个新的对象。
空指针调用成员函数

空指针是可以访问成员函数的,注意有没有用到this指针

用到的话,需要加以判断保证代码的健壮性。

class Q {
public:
	void func1() {
		cout << "func1 called" << endl;
	}

	void func2() {
		if (this == NULL) {
			return;
		}
		// cout << "func2 called" << this->a << endl;
		cout << "func1 called" << a << endl;
	}
	int a;
};
int P::m_B = 24;
int main() {
	Q* q = NULL;
	q->func1();
	q->func2();
}
const 修饰成员函数

常函数:

  • 成员函数厚加const后,这个函数成为常函数
  • 常函数不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数后就可以修改
class Q {
public:
	void func1() {
		cout << "func1 called" << endl;
	}

	void func2() {
		if (this == NULL) {
			return;
		}
		// cout << "func1 called" << this->a << endl;
		cout << "func2 called" << a << endl;
	}
	// this 指针本质上是一个 指针常量  这里的this指针是 Q* const this;
	// 加了const后 的this指针是: const Q* const this
	// 当在函数上 修饰 const后
	void show() const {
		b = 10;
	}
	int a;
	mutable int b;
};
int P::m_B = 24;
int main() {
	Q* q = NULL;
	q->func1();
	q->func2();

}

常对象:

  • 声明对象前面加const——常对象
  • 常对象只能调用常函数
const Q *q ;
友元

友元的目的:让一个函数或者类 能够访问另一个类中私有成员

三种实现:

  • 全局函数做友元(友元函数)

    在类里面用 friend关键字申明某个函数是友元函数,就表明 这个外部函数可以访问这个类的私有属性。

    class friend_method {
    	friend void showInfo();
    private:
    	int a = 10;
    	static int aa;
    };
    int friend_method::aa = 100;
    void showInfo() {
    	cout << "showInfo : " << friend_method::aa << endl;
    }
    int main() {
    	showInfo();
    }
    
  • 类做友元(友元类)

    class A{
        friend class B;
    public:
        ...
    private:
        ...
    }
    class B{
    public:
        ...
    private:
        ...
    }
    
  • 成员函数做友元

    B中的friend_mothod();方法是A的友元

    class A{
        friend void B::friend_mothod();
    public:
        ...
    private:
        ...
    }
    class B{
    public:
        void friend_mothod();
        ...
    private:
        ...
    }
    
运算符重载

概念:对已经有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

利用关键字operator[+ - * / % ......]

  1. 使用成员函数进行重载
#pragma once
#ifndef COMPLEX_UTILS_
#define COMPLEX_UTILS_


class complex {
public:
	double real;
	double img;
	complex(double real, double img);
	~complex();
	complex operator+(const complex& c);
	complex operator-(const complex& c);
	complex operator*(const complex& c);
	complex operator/(const complex& c);
};
#endif

#include "complex_utils.h"

complex::complex(double real, double img):real(real), img(img) {

}
complex::~complex() {

}
complex complex::operator+(const complex& c) {
	return complex(this->real + c.real, this->img + c.img);
}

complex complex::operator-(const complex& c) {
	return complex(this->real - c.real, this->img - c.img);
}

complex complex::operator*(const complex& c) {
	return complex(this->real * c.real - this->img * c.img, this->real * c.img + this->img * c.real);
}

complex complex::operator/(const complex& c) {
	return complex((this->real * c.real + this->img * c.img) / (c.real * c.real + c.img * c.img), 
		(this->img * c.real - this->real * c.img) / (c.real * c.real + c.img * c.img));
}

  1. 使用全局函数进行重载

    complex operator+(const complex& c1, const complex& c2) {
    	return complex(c1.real + c.real, c1.img + c.img);
    }
    
  2. 重载<<运算符(有点类似于java 中的 toString方法)

    // 只有这样写  才可以是 cout << complex << ...
    // 而且要链式编程
    ostream& operator<<(ostream& cout, complex c);
    
    // 
    ostream& operator<<(ostream& cout, complex c) {
    	cout << "(" << c.real << ", " << c.img << ")";
    	return cout;
    }
    
  3. 重载++运算符

    		// 重载前置++
    	complex& operator++();
    	complex operator++(int);
    	complex& operator--();
    	complex operator--(int);
    
    complex& complex::operator++() {
    	++this->real;
    	++this->img;
    	return *this;
    }
    complex& complex::operator++(int) {
    	this->real++;
    	this->img++;
    	return *this;
    }
    complex& complex::operator--() {
    	this->real--;
    	this->img--;
    	return *this;
    }
    complex complex::operator--(int) {
    	complex tp = *this;
    	this->real--;
    	this->img--;
    	return tp;
    }
    
  4. 重载赋值=运算符

    C++默认至少给一个类添加4个函数

    1. 默认无参构造函数
    2. 默认析构函数
    3. 默认拷贝构造函数
    4. 赋值运算符operator=,对属性进行值拷贝

    问题隐患:当p1 和 p2 都是实例对象的时候,若 对象里面存在存放在堆区的属性,执行代码p1=p2,这段代码执行的是编译器的浅拷贝,执行析构函数的时候可能会引起内存的重复释放。

    解决办法:重载运算符=

    class child {
    public:
    	int* p_age;
    	child(int age);
    	~child();
        // 冲下=运算符
        child& operator=(const child& c);
    };
    
    child::child(int age) {
    	p_age = new int(age);
    }
    child::~child() {
    	if (p_age == NULL) {
    		return;
    	}
    	delete p_age;
    }
    // 返回自身 实现连等号的操作 c1 =  c2 = c3;
    child& child::operator=(const child& c) {
    	if (this->p_age != NULL) {
    		delete p_age;
            p_age = NULL;
    	}
    	p_age = new int(*c.p_age);
    	return *this;
    }
    
  5. 重载关系运算符 == >= <= > < !=用作比较器?

  6. 重载函数调用运算符()

    • 由于重载后使用的方式非常像函数的调用,因此也可以成为仿函数
    • 仿函数没有固定的写法,非常灵活
    class Printer {
    public:
    	void operator()(string msg) {cout<<msg<<endl;}
    };
    
    void test_operator_fake_func() {
    	// 匿名对象调用
    	Printer()("123");
    
    	Printer p;
    	p("456");
    }
    int main() {
    	//showInfo();
    	test_operator_fake_func();
    	
    }
    

继承和多态

语法

class subClass:[继承方式(包括public)] superClass { .... };

继承方式:

public:

protected:

private:

一张图搞定继承方式

继承中的对象模型

问题:从父类中继承过来的成员,哪些属于子类对象?

父类中所有非静态成员属性都会被子类继承,父类中的私有属性也会被继承,但是会被编译器隐藏,访问不到。

开发人员提示工具查看对象模型:

  • cmd , 先到文件路径下
  • cl /d1 reportSingleClassLayout类名 文件名
继承——构造和析构的顺序

先构造父类,先析构子类

继承——同名成员怎么处理
  • 访问子类的同名成员可以直接访问
  • 访问父类的同名成员需要加作用域
class Base {
public:
	int d = 10;
    void show() { cout << "Base 中的 show() " << endl;}
};
class Son :public Base {
public:
	int d = 20;
   	void show() {cout << "Son 中的 show()" << endl;	};
};
int main() {
	Son s;
	cout << s.d << endl;
	cout << s.Base::d << endl;
    s.show();
	s.Base::show();
}

  • 子类中同名的成员函数会隐藏父类中所有的同名成员函数。要访问父类的需要加作用域
  • 静态成员和非静态成员 出现了 同名,处理方法一致
多继承语法

语法:class Son: [继承方式] Base1, [继承方式] Base2,...

可能引发父类中的同名成员出现,需要加作用域区分。

多继承在实际开发中不建议。

菱形继承
  • 两个派生类继承同一个基类
  • 某个类同时继承这个两个派生类

问题:继承了很多相同的东西,造成资源浪费

解决办法:虚继承, 关键字virtual,在继承方式前面加这个关键字,然后这个数据就只有一份,三个类共享这一份数据。

虚继承的底层实现底层实现,会产生一个指针vbptr(虚基类指针),这个指针指向一个虚基类表vbtable

多态

多态分为两类:

  • 静态多态:函数重载 和 运算符重载——复用函数名/运算符
  • 动态多态:派生类 和 虚函数 实现运行时多态

静态多态 VS 动态多态

  • 静态多态的函数地址早绑定——编译阶段确定函数地址
  • 动态多态的函数地址晚绑定——运行阶段确定函数地址
class Animal {
public:
	void speak() {
		cout << "Animal 在说话" << endl;
	}
};
class Cat :public Animal {
public:
	void speak() {
		cout << "Cat 在说话" << endl;
	}
};
// 地址早绑定,在编译阶段就 确定了函数的地址 。 不管传什么样的动物,都是执行Animal中的方法
void do_speak(Animal& animal) {
	animal.speak();
}
void test_polymorphic() {
	Cat cat;
	do_speak(cat); // Animal 在说话
}
int main() {
	test_polymorphic();
}

问题:静态多态,不是动态多态。原因是地址的早绑定

解决方法:在要执行的方法前面加关键字virtual,声明这个方法是虚方法,则不会有地址早绑定。

class Animal {
public:
	virtual	void speak() {
		cout << "Animal 在说话" << endl;
	}
};

动态多态满足条件:

  • 有继承关系
  • 子类重写父类的虚方法(子类里面的方法可以加也可以不加关键字 virtual

动态多态的使用:

  • 父类的指针或者引用,执行子类对象

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前后期的扩展和维护
多态的底层原理

底层原理

  • 虚函数会产生一个指针vfptr(虚函数指针),指向一个vftable(虚函数表)
  • 这个表记录虚函数的地址(表中的数据是&[类名]::[虚方法名]
  • 子类会继承父类的虚函数表,当子类重写父类中的虚函数,子类中的虚函数表 内部 会替换成 子类的虚函数地址(&[父类名]::[虚方法名]变成 &[子类名]::[虚方法名]
  • 子类的这个操作不会影响父类的虚函数表,父类的虚函数表还是存在的
  • 当父类的指针或者引用指向子类对象的时候,发生多态。Animal& animal = cat
纯虚函数和抽象类

在多态中,通常父类中的虚函数的实现是毫无意义的,主要调用的是子类重写的内容。

因此可以将虚函数改为纯虚函数

纯虚函数语法virtual 返回值类型 函数名 (参数列表) = 0;

抽象类:类中有纯虚函数

抽象类的特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class AbstractBase {
public:
	// 纯虚函数
	virtual void func() = 0;
};

class Sub : public AbstractBase {
public:
	void func() {
		cout << "实现纯虚函数的抽象类的虚方法" << endl;
	}
};
虚析构和纯虚析构

问题:父类指针在调用析构函数的时候,不会调用子类中的析构函数,如果子类有堆区的属性,堆区的数据就不会释放干净,就会出现内存的泄露。如果是父类的引用,好像没有这个问题

解决办法:将父类的析构函数设置为 虚析构函数

纯虚析构函数虚析构函数=0;

如果父类中有堆区的属性,则必须要实现(纯虚析构函数),纯虚析构函数的实现在类外实现。

class Animal {
public:
	// 关键字 virtual 使得函数变成虚函数。可以实现地址的晚绑定
	virtual void speak() {
		cout << "Animal 在说话" << endl;
	}
	Animal() { cout << "Animal 的 构造函数 " << endl; }
	~Animal() { cout << "Animal 的 析构函数 " << endl; }
};

class Cat :public Animal {
public:
	string* p_name;
	void speak() {
		cout << *p_name <<  "Cat 在说话" << endl;
	}
	Cat(string name) {
		cout << "Cat 的 构造函数 " << endl;
		p_name = new string(name);
	}
	~Cat() {
		cout << "Cat 的 析构函数 " << endl;
		if (p_name != NULL) {
			delete p_name;
			p_name = NULL;
		}
	}
};

void test_Animal_pointer_() {
	Animal* cat = new Cat("Tom");
	cat->speak();
	delete cat;
}

main() {
    test_Animal_pointer_() ;
}

/*结果为*/
/*
Animal 的 构造函数
Cat 的 构造函数
TomCat 在说话
Animal 的 析构函数
*/

在添加关键字 虚析构方法

virtual ~Animal() { cout << "Animal 的 析构函数 " << endl; }
class Animal{
    virtual ~Animal() = 0;
}
Animal::~Animal() {
    // 纯虚析构函数 必须要有 具体实现,不然父类有可能有内存泄漏
}

文件操作

#include <fstream>

文件:

  • 文本文件:文本以ASCII码存储
  • 二进制文件:文本以二进制存储

操作文件的三大类:

  • ofstream:写
  • ifstream:读
  • fstream:读写

文件打开方式

打开方式解释
ios::in
ios::out
ios::ate初始位置:文件尾
ios::app追加方式写文件
ios::trunc若文件存在,先删除,再创建
ios::binary二进制
文本文件——写

步骤:

  1. 包含头文件

    #include <fstream>
    
  2. 创建流对象

    ofstream ofs;
    
  3. 打开文件

    ofs.open("path", 打开方式);
    

    如果混用方式打开文件,利用 |操作符 ios::binary | ios::out

  4. 写数据

    ofs << "写入的数据" ;
    
  5. 关闭文件

    ofs.close();
    
void test_file_write() {
	string file = "text.txt";
	ofstream ofs;
	ofs.open(file, ios::out);
	ofs << "公开日记" << endl;
	ofs << "仙人掌的花蕊会动" << endl;
	ofs << "刚拽完妹妹养的兔子的尾巴," << endl;
	ofs << "好家伙,刚刚在隔壁吃花,现在又玩花,天天围着花转,您是花狐蝶吧" << endl;
	ofs.close();
}
文本文件——读

步骤:

  1. 包含头文件
  2. 创建流对象
  3. 打开文件
  4. 读取文件
  5. 关闭文件
void test_file_read() {
	string file = "text.txt";
	ifstream ifs;
	ifs.open(file, ios::in);
	if (!ifs.is_open()) {
		cout << "请检查文件路径" << endl;
	}
	// 方式1
	/*
	char buff[1024] = {0};
	while (ifs >> buff) {
		cout << buff << endl;
	}
	*/

	/*
	// 方式2
	char buff[1024] = { 0 };
	while (ifs.getline(buff, sizeof(buff))) {
		cout << buff << endl;
	}
	*/


	// 方式3
	/*
	string buff;
	while ((getline(ifs, buff))) {
		cout << buff << endl;
	}
	*/

	// 方式4
	char bu;
	while ((bu = ifs.get()) != EOF) {
		cout << bu;
	}

	

	ifs.close();
	
}
二进制文件——写

打开方式:ios::binary

要用C语言的字符数组写,尽量不要用C++的string写

流程一样,可以在创建流对象的时候就加入文件 和 打开方式的信息

需要用到的一个重要方法:ostream& write(const char * buffer, int len);字符指针buffer指向内存中一段存储空间,len是读写的字节数

void test_binary_file_write() {
	Per p = {"张三", 15};
	ofstream ofs("per.txt", ios::out | ios::binary);
	ofs.write((const char *)&p, sizeof(Person));
	ofs.close();
}
二进制文件——读

重要函数:istream& read(char * buffer, int len);,字符指针buffer指向内存中一段存储空间,len是读写的字节数。

#include <fstream>
void test_binary_file_read() {
	ifstream ifs;
	ifs.open("per.txt", ios::in | ios::binary);
	if (!ifs.is_open()) {
		return;
	}
	Per p;
	ifs.read((char *) & p, sizeof(Per));
	cout << p.name << ", " <<  p.age << endl;
	ifs.close();
}

提高编程

模板

  • c++提供泛型编程,主要的技术就是模板
  • c++提供两种模板机制:函数模板类模板
函数模板

作用:建立一个通用函数,其函数返回值和形参类型可以是不具体制定,用一个虚的类型表示。将类型参数

语法

template<typename T>  // typename 也可以换成 class,typename一般指函数模板 ,class一般指类模板, 但是两者没有实质性的区别
// 函数声明 或者 定义
.....

使用的时候,有两种调用方式:

  • 自动类型推到
  • 显示指定类型
template<typename T>
void swap_(T& a, T& b) {
	T t = a;
	a = b;
	b = t;
}
void test_func_template() {
	int a = 10;
	int b = 20;
	// 编译器自动推导类型
	swap_(a, b);
	// 指示编译器具体类型
	//swap_<int>(a, b);
	cout << a << "," << b << endl;
}

int main() {
	test_func_template();
}
函数模板的注意事项

注意:

  • 自动类型推到,必须推导出一直的数据类型T,才可以使用
  • 模板必须要确定出T的数据类型,才可以使用
案例

函数模板实现对不同数据类型的数组进行排序

函数模板 与 普通函数的区别
  • 普通函数在调用的时候可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时
    • 如果用自动类型转换,不会发生隐式类型转换
    • 如果用显示指定类型的方式,可以发生饮食类型转换
函数模板 与 普通函数的调用规则

规则

  • 函数模板也可以发生重载
  • 如果函数模板和普通函数都可以调用, 优先调用普通函数
  • 如果函数模板可以产生更好的匹配,优先调用函数模板。(更好的匹配 指的是 编译器可能做更少的调用工作,比如没有类型转换)
  • 可以通过空模板参数列表强制调用函数模板。 [函数名]<>(参数列表);
模板的局限性

问题:函数模板的实现 使得类型参数无法保证全部类型,如果传入的类型有问题,则编译器会在运行的时候报错。

解决方法:对于特定的类型,要实现 具体化的代码

// 模板实现
template<typename T>
bool equals_(T& t1, T& t2) {
	return t1 == t2;
}
// 具体化实现
template<> bool equals_(Cat& c1, Cat& c2) {
	return c1.p_name == c2.p_name;
}
类模板

作用:建立一个通用类,类中的成员可以不具体指定,用一个虚拟的类型代表。

语法:

template<class T1, class T2>
// 定义一个节点模板类
template<typename K, typename V>
class node {
public:
	K k;
	V v;
	node(K k, V v) {
		this->k = k;
		this->v = v;
	}
};

// 使用
main() {
    node<string, int> n("八戒", 250);
}
类模板和函数模板的区别
  • 类模板没有自动类型推导的使用方式,必须指定类型

  • 类模板在模板参数列表中可以有默认参数

    template<typename K, typename V = int> // 指定V类型参数的默认值为int
    class node {...}
    
类模板中成员函数创建的时机
  • 普通类的成员函数一开始就创建
  • 类模板中的成员函数在调用的时候创建
类模板对象作为函数的参数

传参方式:

template<typename K, typename V>
class node {
public:
	K k; V v;
	node(K k, V v) { this->k = k; this->v = v;}
	void show() { cout << "(" << k << ", " << v << ")" << endl;}
};
  • 指定传入类型——直接显示对象的数据类型(简单,用得最多)

    // 第一种方式,指定类模板的类型参数
    void test_class_template_parma_01(node<string, string> &n) {
    	cout << n.k << ", " << n.v << endl;
    }
    
  • 参数模板化——将对象中的参数变为模板进行传递

    // 第二种方式,使用函数模板
    template<typename K, typename V>
    void test_class_template_parma_02(node<K, V>& n) {
    	cout << n.k << ", " << n.v << endl;
    	// 查看数据类型参数 到底是什么数据类型
    	cout << typeid(K).name() << ", " << typeid(V).name() << endl;
    }
    
  • 整个类模板化——将这个对象类型 模板化进行传递

    // 第三种方式,将整个类都模板化
    template<class T>
    void test_class_template_param_03(T &t) {
    	t.show();
        // 查看类型参数
    	cout << typeid(t).name() << endl;
    }
    
类模板与继承

注意一下几点:

  • 子类继承一个类模板父类的时候,子类要声明父类中类型参数的具体值。 如果不指定,编译器无法给子类分配内存。
  • 如果想灵活指定父类中类型参数,子类也需要用模板类
类模板成员函数的类外实现

要加模板

template<class K, class V>
[返回类型] [类名]::[函数名](参数列表) {具体实现}
类模板分文件编写
类模板与友元
  • 全局函数类内实现——直接在类内声明友元
template<typename K, typename V>
class node {
	friend void show_node_info(node<K,V> no) {	cout << "(" << no.k << ", " << no.v << ")" << endl;	}
public:
	K k;
	V v;
	node(K k, V v) {
		this->k = k;
		this->v = v;
	}
	void show() {cout << "(" << k << ", " << v << ")" << endl;}
};
void test_friend_func() {
	node<string, int> no("fq", 18);
	show_node_info(no);
}
int main() {
	test_friend_func();
}
  • 全局函数类外实现——需要提前让编译器知道全局函数的存在
#include<string>
#include<iostream>
using namespace std;

// 先声明有这么一个类
template<class K, class V>
class _node;

// 再声明有这么一个函数
template<class K, class V>
void show_info(_node<K, V> n) {
	cout << "(" << n.k << ", " << n.v << ")" << endl;
}
// 具体这个类
template<class K, class V>
class _node {
	// 类内声明,类外实现,需要加一个空的参数列表
	friend void show_info<>(_node<K, V> n);
private:
	K k;
	V v;
public:
	_node(K k, V v) :k(k), v(v) {}
	~_node() {}
};

// test
void test_friend_func_() {
	_node<string, int> n("fq", 18);
	show_info(n);
}
int main() {
	test_friend_func_();
	return 0;
}

注意:类模板的成员函数只有在调用的时候才会创建,所以不建议分文件写开(.h 和 .cpp),直接将类模板卸载一个 .hpp文件中。

类模板的案例

请添加图片描述请添加图片描述

#pragma once // 保证头文件只会被编译器编译一次 也可以用 #ifndef #define #endif 来实现

#include<iostream>
using namespace  std;

template<class T>
class static_array {
private:
	T* addr_ptr;
	int capacity;
	int size;
public:
	static_array(int capacity) {
		cout << "有参构造函数 被 调用" << endl;
		this->capacity = capacity;
		this->size = 0;
		this->addr_ptr = new T[capacity];
	}
	static_array(const static_array& arr) {
		cout << "拷贝构造函数 被 调用" << endl;
		//check(this);
		if (addr_ptr != NULL) {
			delete[] addr_ptr;
			addr_ptr = NULL;
		}
		this->addr_ptr = new T[arr.capacity];
		this->capacity = arr.capacity;
		this->size = arr.size;
		// 拷贝
		for (int i = 0; i < arr.size; i++) {
			addr_ptr[i] = arr.addr_ptr[i];
		}
	}
	static_array& operator=(const static_array & arr) {
		cout << "operator=函数 被 调用" << endl;
		//check(this);
		if (addr_ptr != NULL) {
			delete[] addr_ptr;
			addr_ptr = NULL;
		}
		this->addr_ptr = new T[this->capacity];
		this->capacity = arr.capacity;
		this->size = arr.size;
		for (int i = 0; i < arr.size; i++) {
			addr_ptr[i] = arr.addr_ptr[i];
		}
		return *this;
	}

	~static_array() {
		cout << "析构函数 被 调用" << endl;
		//check(this);
		if (addr_ptr != NULL) {
			delete[] addr_ptr;
			addr_ptr = NULL;
		}
	}

	// 尾填法
	void push_back(const T& t) {
		if (size >= capacity)
			return;
		addr_ptr[++size] = t;
	}
	// 尾删法 并且返回这个元素
	T pop() {
		return addr_ptr[size--];
	}
	// 通过下表访问数组元素 (函数法,[])
	T& operator[](const int& index) {
		return addr_ptr[index];
	}
	T index(const int& index) {
		return addr_ptr[index];
	}
	// 获取当前的size  和 容量
	int get_size() {
		return size;
	}
	int get_capacity() {
		return capacity;
	}
};

STL

string容器

本质:一个类,c++风格的字符串

string 和 char*的区别:

  • char* 是一个指针
  • string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器

特点

  • 封装了很多成员方法
  • 管理内存,不用担心越界
string 的构造函数
  • string():创建一个空的字符串
  • string(const char* s);:使用字符串s初始化
  • string(const string& str);:拷贝
  • string(int n, char c);:使用n个字符c初始化
赋值操作
  • string& operator=(const char *s)
  • string& operator=(const string& s)
  • string& operator=(char s):肌肤赋给当前字符串
  • string& assign(const char *s):用字符串s赋值给当前字符串
  • string& assign(const char *s, int n):将字符串s的前n个字符赋给当前字符串
  • string& assign(const string& s):用字符串s赋值给当前字符串
  • string& assign(int n, char c):用n个字符赋值给当前字符串
拼接操作
  • string& operator+=(const char* str)
  • string& operator+=(const char c)
  • string& operator+=(const string* str)
  • string& append(const char* s):字符串s 拼接到当前字符串的末尾
  • string& append(const char* s, int n):字符串前n个字符连接到当前字符的末尾
  • string& append(const string& s)
  • string& append(const string& s, int pos, int n):字符串s中从pos开始的n个字符连接到字符串结尾
查找与替换

查找:查找指定字符串是否存在

替换:在指定位置替换字符串

  • int find(const string& str, int pos = 0) cosnt;: 查找str第一次出现的位置,从pos位置开始

  • int find(const char *s , int pos = 0) cosnt;:查找s第一次出现的位置,从pos开始

  • int find(const char * s, int pos, int n) cosnt;:从pos位置找前n个字符第一次位置

  • int find(const cahr c, int pos = 0) cosnt;:查找字符c第一次出现的位置,pos:开始位置

  • int rfind(const string& str, int pos = npos) cosnt;: 找最后出现的位置

  • int rfind(const char * s, int pos, int n) cosnt;:

  • int rfind(const char c, int pos = 0) cosnt;

  • string& replace(int pos, int n, const string& str);:替换从pos开始n个字符的字符串str

  • string& replace(int pos, int n, const char* s);:替换从pos开始n个字符的字符串s

比较

方式:逐个按照字符的ASCII码比较。 = 0 >1 < -1

  • int compare(const string& s) const;
  • int compare(const char* s)
字符存取
  • char& operator[](int n)
  • char& at(int index);
插入和删除
  • string& insert(int pos, const char* s);
  • string& insert(int pos, const string& str);
  • string& insert(int pos, const char c);
  • string& erase(int pos, int n = epos);:删除从pos开始的n个字符
子串获取
  • string substr(int pos, int n =eops);:返回从pos开始的n个字符组成的字符串

vector容器

功能:单端数组,可动态扩展数组长度[动态扩展是申请更大的空间,而不是在原有的空间上扩展]。插入和删除的时候,数组头不变,在数组尾部发生变化。

构造函数
  • vector<T> v;: 默认构造函数
  • vector(v.begin(), v.end());: 将[begin(), end() ) 区间的元素拷贝给容器
  • vector(n, elem);: 初始化 n 个elem 给容器
  • vector(const vector& vec);:
赋值操作
  • vector& operator=(const vector& vec);:
  • assign(beg, end)
  • assign(n, elem)
容量和大小
  • empty():判断是否为空
  • capacity():容器的容量
  • size():容器里元素的个数
  • resize(itn num):重新指定容器长度
  • resize(int num, T elem):重新指定容器长度,并用 elem为默认填充值
插入和删除
  • push_back(ele):尾部插入一个元素
  • pop_back():删除尾部的元素
  • insert(cont_iterator pos, ele):迭代器指向位置pos插入一个元素
  • insert(const_iterator pos, int count, ele):迭代器指向位置pos插入count个元素
  • erase(const_iterator pos):产出迭代器指向的元素
  • erase(const_iterator start, const_iterator end):删除迭代器指向的从start到end之间的元素
  • clear():清空容器
数据存取
  • at(int index):返货index所指的数据
  • operator[](int index):同上
  • front():返回第一个
  • back():返回最后一个
互换容器

实现两个容器内元素的互换

swap(vec):将vec与本身进行互换。

用途:收缩内存空间

void test_swap_vec2() {
	vector<int> v1;
	for (int i = 0; i < 1000000; i++) {
		v1.push_back(i);
	}
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;

	v1.resize(3);

	cout << v1.size() << endl;
	cout << v1.capacity() << endl;
    /*
    1000000
	1049869
	3
	1049869
    */
}
void test_swap_vec2() {
	vector<int> v1;
	for (int i = 0; i < 1000000; i++) {
		v1.push_back(i);
	}
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;

	v1.resize(3);
	vector<int>(v1).swap(v1); // 用匿名对象和本身互换,可以给容器瘦身,匿名对象用完后就被编译器清楚了

	cout << v1.size() << endl;
	cout << v1.capacity() << endl;
/*1000000
1049869
3
3*/
}
预留空间
  • 功能:减少vector在动态扩展容量的时候的扩展次数

  • reserve(int len):容器预留len个元素长度,预留位置不初始化,元素不可以访问。

  • 当容器需要存储的元素较多,可以先申请比较大的空间,避免频繁的扩容

deque容器

  • 双端数组,头部和尾部都可以插入删除 (队列)

deque 和 vector的区别

  • vector访问 比 deque 快
  • deque 插入删除效率高

内部维护了一个中控器,维护每一段缓冲区的内容,缓冲区存放真是的数据,中控器存放缓冲区的地址

迭代器支持随机访问

构造函数
  • deque<t>deqt:默认
  • deque(beg, end):左开右闭区间拷贝到本身
  • deque(n, elem):将n个elem拷贝给本身
  • deque(const deque &deq):拷贝构造
赋值操作
  • deque& operator=(const deque<T> &deq)
  • assign(beg, end)
  • assign(n, elem)
大小

没有容量,只有大小

  • empty()
  • size()
  • resize()
  • resize(int num, elem)
插入删除

两端插入:

  • push_back(elem)
  • push_front(elem)
  • pop_back(elem)
  • pop_front(elem)

指定位置操作:

  • insert(pos, elem):pos是一个迭代器,在pos位置插入一个elem
  • insert(pos , n, elem):pos是一个迭代器,在pos位置插入n个elem
  • insert(pos, beg, end):在pos位置插入区间[beg, end)
  • clear():清空
  • erase(beg, end):删除区间[beg, end)的数据,返回下一个数据的位置
  • erase(pos):删除指定pos位置的数据,返回下一个数据的位置
数据存取
  • at(int index)
  • operator[]
  • front()
  • back()
排序操作
  • 利用算法对deque容器进行排序
  • sort(iterator beg, iterator end):对 beg 和end区间内的元素进行排序

stack容器

先进后出

构造函数
  • stack<t> stk
  • stack(const stack& stk)
赋值操作
  • stack& operator=(const stack& stk)
数据存取
  • push(elem):入栈
  • pop():出栈
  • top():获取栈顶元素
大小
  • empty():判断是否为空
  • size():获取元素个数

queue容器

先进先出

构造函数
  • queue<T> que
  • queue(const queue& que):拷贝构造
赋值
  • queue& operator=(const queue& que)
数据存取
  • push(elem):入队
  • pop():出队
  • back():获取队尾的元素
  • front():获取对头的元素
大小操作
  • empty()
  • size()

list容器

链表:由节点构成。

节点:由数据域和指针域构成。

迭代器不支持随机访问,只能前移和后移,不能跳跃。

构造函数
  • list<T> lst
  • list(beg, end)
  • list(n , elem)
  • list(const list& lst)
赋值和交换
  • list& operator=(const list& l)
  • assign(beg, end)
  • assign(n, elem)
  • swap(const list& l)
大小
  • size()
  • empty()
  • resize()
  • resize(num, elem)
插入和删除
  • push_back(elem)
  • pop_back()
  • push_front(elem)
  • pop_front()
  • insert(pos, elem)
  • insert(pos, n, elem)
  • insert(pos, beg, end)
  • clear()
  • erase(beg, end):删除区间[beg, end)的数据,返回下一个数据的位置
  • erase(pos):删除第pos个元素
  • remove(elem):删除容器中所有与elem匹配的元素
数据存取
  • front():返回第一个元素
  • back():返回最后一个元素

没有通过index的 方式访问元素

反转和排序
  • reverse():反转链表

  • sort():是内部的函数,默认是升序。

    所有不支持的随机访问的迭代器,不可以使用标准算法。
    不支持随机访问迭代器的容器,内部会提供对应的一些算法。
    可以写一个函数 提供比较规则

    void showList(const list<T>& li) {
    	for (auto ele : li) {cout << ele << " ";}
    	cout << endl;
    }
    // 提供排序规则 根据字母表排序
    bool compare(string v1, string v2) {
    	int len1 = v1.length();
    	int len2 = v2.length();
    	for (int i = 0; i < min(len1, len2); i++) {
    		if (v1[i] > v2[i]) {
    			return true;
    		} else if (v1[i] < v2[i]) {
    			return false;
    		}
    	}
    	return len1 >= len2 ? true : false;
    }
    
    void testListInsertDel() {
    	list<string> strList;
    	strList.push_back("james");
    	strList.push_back("wade");
    	strList.push_back("kobe");
    	strList.push_back("jorden");
    	strList.push_back("tim");
    	strList.push_back("harden");
    
    	showList(strList);
    	strList.sort(compare);
    	showList(strList);
    }
    

set/multiset容器

关联式容器,底层用二叉树实现。

所有的元素在插入时自动会自动排序。

set 与 multiset的区别:

  • set 不能有重复的元素
  • multiset允许有重复的元素
构造和赋值

构造:

  • set<T> st;
  • set(const set& st)

赋值:

  • set& operator=(const set& st)
大小和交换
  • size()
  • empty()
  • swap(st)
插入和删除
  • insert(elem)
  • clear()
  • erase(pos):删除指定位置的元素
  • erase(beg, end):删除一个区间的元素
  • erase(elem):删除容器中的elem元素
查找和统计
  • find(elems):查找元素是否存在,返回该元素的迭代器,如果不存在,就返回set.end()
  • count(elems):统计元素elem的个数
set 与 multiset 的区别
pair对

成对出现的数据,利用对组返回两个数据

  • pair<type, type> p(value1, value2)
  • pair<type, type> p = make_pair(value1, value2)
void testPairAPI() {
	pair<string, int> p("tom", 90);
	cout << p.first << " " << p.second << endl;
}
set 排序

自定义set容器的排序规则

  • 利用仿函数(重载()运算符),可以改变排序规则

    class MyCompare {
    public:
    	bool operator()(int v1, int v2) const { return v1 > v2; }
    };
    void testSetMySort() {
    	set<int, MyCompare> s;
    	s.insert(20);
    	s.insert(10);
    	s.insert(30);
    	s.insert(40);
    	s.insert(50);
    	s.insert(60);
    	for (set<int, MyCompare>::iterator it = s.begin(); it != s.end(); it++) {
    		cout << *it << " ";
    	}
    	cout << endl;
    }
    
  • 存放自定义数据类型都需要指定自定义排序规则

    class Person {
    public:
    	string name;
    	int age;
    	Person(string name, int age) {
    		this->name = name;
    		this->age = age;
    	}
    };
    class PersonCompare {
    public:
    	bool operator()(Person p1, Person p2) const { return p1.age > p2.age; }
    };
    void testSetMySort() {
    	set<Person, PersonCompare> s2;
    	Person p1("james", 36);
    	Person p2("kobe", 40);
    	Person p3("wade", 38);
    	Person p4("madi", 40);
    	Person p5("harden", 31);
    	s2.insert(p1);
    	s2.insert(p2);
    	s2.insert(p3);
    	s2.insert(p4);
    	s2.insert(p5);
    
    	for (set<Person, PersonCompare>::iterator it = s2.begin(); it != s2.end(); it++) {
    		cout << it->name << " " << it->age << endl;
    	}
    
    }
    

map 容器

本质:关联式容器,底层用二叉树实现

所有插入的元素都会根据key值进行排序

优点:

  • 可以根据key快速找到value

map 与 multimap 的区别

  • map不允许有重复的可以
  • multimap有重复的key
构造赋值
  • map<K,V> mp
  • map(const map& mp)
  • map& operator=(const map& mp)
// 打印map
template<class K, class V>
void printMap(const map<K, V>& mp) {
	for (auto m : mp) {
		cout << m.first << ": " << m.second << endl;
	}
}
void testMapAPI() {
	map<string, int> mp;
	pair<string, int> p1("james", 40000);
	pair<string, int> p2("kobe", 34000);
	pair<string, int> p3("harden", 20000);
	pair<string, int> p4("yao", 10000);
	mp.insert(p1);
	mp.insert(p2);
	mp.insert(p3);
	mp.insert(p4);
	mp.insert(pair<string, int>("durent", 30000));

	printMap(mp);
	map<string, int> clMap;
	clMap = mp;
	printMap(clMap);
}
大小和交换
  • size()
  • empty()
  • swap(mp)
插入删除
  • insert(elem)
  • clear():清空
  • erase(pos):删除pos迭代器所指的元素
  • erase(beg, end):左闭右开
  • erase(key):根据key删除 key-value对

查找统计

  • find(key):如果存在key,则返回该键的迭代器;否则返回map.end()
  • count(key):统计key的个数
map 排序

利用仿函数

class CompareMap {
public:
	bool operator()(int k, int l) const { return k > l; }
};
void testMapSort() {
	map<int, int, CompareMap> mp1;
	mp1.insert(make_pair(1, 36));
	mp1.insert(make_pair(3, 36));
	mp1.insert(make_pair(2, 36));
	mp1.insert(make_pair(4, 36));
	mp1.insert(make_pair(5, 36));
	for (auto m : mp1) { cout << m.first << ": " << m.second << endl; }
}

函数对象

概念:

  • 重载函数调用操作符的类
  • 函数对象使用重载的()时,行为类似函数的调用,也叫仿函数

本质:函数对象是一个类,不是一个函数

函数的对象的使用
  • 使用的时候,可以像普通函数那样调用,可以有参数,可以有返回值

  • 函数对象可以有自己的状态

  • 函数对象可以作为参数传递

    class Print {
    public:
    	void operator()(string msg) { cout << msg << endl; }
    	int cnt; // ()调用的次数
    	Print() { cnt = 0; }
    };
    void doPrint(Print& print, string msg) {
    	print(msg);
    }
    
    void testFuncObje() {
    	Add add;
    	Print p;
    	cout << add(1, 3) << endl;
    	p("Hello world");
    	doPrint(p, "doPrint");
    }
    
谓词
  • 返回bool类型的仿函数
  • 如果operator接受n个参数,叫做n元谓词
一元谓词 二元谓词
// 一元谓词
class GreaterFive {
public:
	bool operator()(int val) { return val > 5; }
};
// 二元谓词
class Jiangxu {
public:
	bool operator()(int v1, int v2) { return v1 > v2; }
};

void testJiangxu() {
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}
	sort(v.begin(), v.end(), Jiangxu());
	for (auto vv : v) {
		cout << vv << endl;
	}
}

void tesGreaterFivet() {
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}
	vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
	if (it == v.end()) {
		cout << "不存在" << endl;
	} else {
		cout << "存在 :" << *it << endl;
	}
}
内建函数对象

用法:

  • 用法和一般函数完全相同
  • #include<functional>

分类:

  • 算术仿函数
    • template<class T> T plus<T>
    • template<class T> T minus<T>
    • template<class T> T multiplies<T>
    • template<class T> T divides<T>
    • template<class T> T modulus<T>
    • template<class T> T negate<T>
  • 关系仿函数
    • template<class T> bool equal_to<T>
    • template<class T> bool not_equal_to<T>
    • template<class T> bool greater_equal<T>
    • template<class T> bool less<T>
    • template<class T> bool less_equal<T>
  • 逻辑仿函数
    • template<class T> bool logical_and<T>
    • template<class T> bool logical_or<T>
    • template<class T> bool logical_not<T>
void testFakeFunc() {
	plus<int> p;
	cout << p(1, 3) << endl;

	vector<int> v;
	for (int i = 0; i < 15; i++) {
		v.push_back(i);
	}
	sort(v.begin(), v.end(), greater<int>());
	for (auto ele : v) {
		cout << ele << " ";
	}
	cout << endl;

	vector<bool> w;
	w.push_back(true);
	w.push_back(false);
	w.push_back(false);
	w.push_back(true);
	vector<bool> w1;
	w1.resize(w.size());
	// 将w容器的元素取反,搬运到w1中
	transform(w.begin(), w.end(), w1.begin(), logical_not<int>());
	for (auto el : w1) {
		cout << el << endl;
	}
}

STL常用算法

  • 在头文件 :
    • <algorithm>:涉及到比较,查找,交换,遍历,复制,修改
    • <functional>
    • <numeric>
遍历算法
  • for_each(iterator beg, iterator end, _func)

    
    void myPrint(int val) { cout << val << " "; }
    class MyPrint {
    public:
    	void operator()(int val) { cout << val << " "; }
    };
    
    void testForEach() {
    	vector<int> v;
    	for (int i = 0; i < 10; i++) {
    		v.push_back(i);
    	}
    	// 1. 利用普通函数
    	for_each(v.begin(), v.end(), myPrint);
    	// 2. 利用仿函数
    	for_each(v.begin(), v.end(), MyPrint());
    }
    
  • transform(iterator beg1, iterator end1, iterator beg2, _func)

    int myTransform(int val) { return val + 10; }
    class MyTransform {
    public:
    	int operator()(int val) { return val * 100; }
    };
    void testTransform() {
    	vector<int> v;
    	for (int i = 0; i < 10; i++) {
    		v.push_back(i);
    	}
    	vector<int>w;
    	w.resize(v.size());
    	//transform(v.begin(), v.end(), w.begin(), MyTransform());
    	transform(v.begin(), v.end(), w.begin(), myTransform);
    	for_each(w.begin(), w.end(), myPrint);
    
    }
    
查找算法
  • find(beg, end, elem):
    • elem是基本数据类型
    • elem是自定义数据类型,需要在类里面重载 operator==
  • adjacent_find(iterator beg, iterator end):查找相邻重复元素,如果查到了,返回相邻元素的第一个迭代器,否则返回 .end()
  • binary_find(iterator beg, iterator end, value):二分查找指定元素是否存在,查到返回true 否则false。必须是有序的数组
  • count(iterator beg, iterator end, value):统计value的个数
  • count_if(iterator beg, iterator end, _Pred):按照条件统计
排序算法
  • sort(iterator beg, iterator end, _Pred)

  • random_shuffle(iterator beg, iterator end):乱序,将容器中的元素”洗牌“

    #include <ctime>
    void testRandomShuffle() {
    	vector<int> v;
    	for (int i = 0; i < 10; i++) {
    		v.push_back(i);
    	}
    	srand((unsigned int)time(NULL));
    	random_shuffle(v.begin(), v.end());
    	for_each(v.begin(), v.end(), myPrint);
    }
    
  • merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest):将两个容器合并吗,存储到另一个容器中。两个容器必须是有序的,dest是目标容器起始迭代器。

    void testMerge() {
    	vector<int> v;
    	for (int i = 0; i < 10; i++) {
    		v.push_back(i);
    	}
    	vector<int>w;
    	for (int i = 10; i < 20; i++) {
    		w.push_back(i);
    	}
    	vector<int> dest;
    	dest.resize(v.size() + w.size());
    	merge(v.begin(), v.end(), w.begin(), w.end(), dest.begin());
    	for_each(dest.begin(), dest.end(), myPrint);
    }
    
  • revrse(iterator beg, iterator end)

拷贝替换
  • copy(iterator beg, iterator end, iterator dest)
  • replace(iterator beg, iterator end, oldValue, newValue)
  • replace_if(iterator beg, iterator end, _Pred, newValue)
  • swap(container c1, container c2):必须是同一种容器
算术生产算法

头文件#include<numeric>

  • accumulate(iterator beg, iterator end, startValue):计算容器的累计总合。startValue为计算起始值
  • fill(iterator beg, iterator end, fillValue):填充指定元素fillValue
  • set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest):求两个容器的交集dest为目标容器的起始迭代器,返回目标容器的.end()迭代器。要求两个集合必须是有序序列
  • set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest):求两个容器的并集,参数和返回值同上
  • set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest):求差集,参数和返回值同上
// 求并集  交集 差集
void testDUI() {
	vector<int> v;
	vector<int> w;
	vector<int> x;
	v.push_back(1);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(7);
	v.push_back(2);
	w.push_back(5);
	w.push_back(10);
	w.push_back(6);
	w.push_back(7);
	x.push_back(1);
	x.push_back(4);

	sort(v.begin(), v.end());
	sort(w.begin(), w.end());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;
	for_each(w.begin(), w.end(), myPrint);
	cout << endl;
	for_each(x.begin(), x.end(), myPrint);
	cout << endl;

	vector<int> vwInter;
	vwInter.resize(min(v.size(), w.size()));
	vector<int>::iterator vwInterEnd = set_intersection(v.begin(), v.end(), w.begin(), w.end(), vwInter.begin());
	for_each(vwInter.begin(), vwInterEnd, myPrint);
	cout << endl;
	vector<int> vwUnion;
	vwUnion.resize(v.size() + w.size());
	vector<int>::iterator vwUnionEnd = set_union(v.begin(), v.end(), w.begin(), w.end(), vwUnion.begin());
	for_each(vwUnion.begin(), vwUnionEnd, myPrint);
	cout << endl;

	vector<int> vxDiff;
	vxDiff.resize(abs((int)(v.size() - x.size())));
	vector<int>::iterator vxDiffEnd = set_difference(v.begin(), v.end(), x.begin(), x.end(), vxDiff.begin());
	for_each(vxDiff.begin(), vxDiffEnd, myPrint);	
}

其他技巧

命名空间

防止名称重复

关键字namespace

C/C++混合开发

使用的关键字 extern "C"。这个关键字表示被修饰的代码按照C语言的编译器编译。

问题:混合编程的时候,为了让C语言的库能够无差别的被C++程序和C程序调用,需要在库的头文件上加代码。

#ifdef __cplusplus
extern "C" {
#endif

// 中间是所有的声明
#include<stdio.h>
double sum(double v1, double v2);

#ifdef __cplusplus
}
#endif

原因:C++编译器会给C++文件/环境添加上一个宏__cplusplus,所以需要增加一个判断,就能无差别切换。

避免头文件重复编译

  1. 头文件里面加上代码#pragma once,就可以了。(要去编译器比较新)

  2. 手动的给每一个头文件定义一个独一无二的宏,再加上一些判断。(对编译器版本没有要求)

    #ifndef MATH_UTILS_
    #define MATH_UTILS_
    // 中间是所有的声明代码
    #endif
    

内联函数 和 宏

内联函数 不需要 调用函数的开销。所以开销小,速度快。

不超过十行的代码,经常被调用,就把这个行数改为内联函数。

tips:将vs弄成release 模式,然后vs设置打开优化。
r end, iterator dest)```

  • replace(iterator beg, iterator end, oldValue, newValue)
  • replace_if(iterator beg, iterator end, _Pred, newValue)
  • swap(container c1, container c2):必须是同一种容器
算术生产算法

头文件#include<numeric>

  • accumulate(iterator beg, iterator end, startValue):计算容器的累计总合。startValue为计算起始值
  • fill(iterator beg, iterator end, fillValue):填充指定元素fillValue
  • set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest):求两个容器的交集dest为目标容器的起始迭代器,返回目标容器的.end()迭代器。要求两个集合必须是有序序列
  • set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest):求两个容器的并集,参数和返回值同上
  • set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest):求差集,参数和返回值同上
// 求并集  交集 差集
void testDUI() {
	vector<int> v;
	vector<int> w;
	vector<int> x;
	v.push_back(1);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(7);
	v.push_back(2);
	w.push_back(5);
	w.push_back(10);
	w.push_back(6);
	w.push_back(7);
	x.push_back(1);
	x.push_back(4);

	sort(v.begin(), v.end());
	sort(w.begin(), w.end());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;
	for_each(w.begin(), w.end(), myPrint);
	cout << endl;
	for_each(x.begin(), x.end(), myPrint);
	cout << endl;

	vector<int> vwInter;
	vwInter.resize(min(v.size(), w.size()));
	vector<int>::iterator vwInterEnd = set_intersection(v.begin(), v.end(), w.begin(), w.end(), vwInter.begin());
	for_each(vwInter.begin(), vwInterEnd, myPrint);
	cout << endl;
	vector<int> vwUnion;
	vwUnion.resize(v.size() + w.size());
	vector<int>::iterator vwUnionEnd = set_union(v.begin(), v.end(), w.begin(), w.end(), vwUnion.begin());
	for_each(vwUnion.begin(), vwUnionEnd, myPrint);
	cout << endl;

	vector<int> vxDiff;
	vxDiff.resize(abs((int)(v.size() - x.size())));
	vector<int>::iterator vxDiffEnd = set_difference(v.begin(), v.end(), x.begin(), x.end(), vxDiff.begin());
	for_each(vxDiff.begin(), vxDiffEnd, myPrint);	
}

其他技巧

命名空间

防止名称重复

关键字namespace

C/C++混合开发

使用的关键字 extern "C"。这个关键字表示被修饰的代码按照C语言的编译器编译。

问题:混合编程的时候,为了让C语言的库能够无差别的被C++程序和C程序调用,需要在库的头文件上加代码。

#ifdef __cplusplus
extern "C" {
#endif

// 中间是所有的声明
#include<stdio.h>
double sum(double v1, double v2);

#ifdef __cplusplus
}
#endif

原因:C++编译器会给C++文件/环境添加上一个宏__cplusplus,所以需要增加一个判断,就能无差别切换。

避免头文件重复编译

  1. 头文件里面加上代码#pragma once,就可以了。(要去编译器比较新)

  2. 手动的给每一个头文件定义一个独一无二的宏,再加上一些判断。(对编译器版本没有要求)

    #ifndef MATH_UTILS_
    #define MATH_UTILS_
    // 中间是所有的声明代码
    #endif
    

内联函数 和 宏

内联函数 不需要 调用函数的开销。所以开销小,速度快。

不超过十行的代码,经常被调用,就把这个行数改为内联函数。

tips:将vs弄成release 模式,然后vs设置打开优化。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值