利用汇编挖掘编程语言的本质
误区:不要相信非一手的中文资料
建议:
- 官方文档最重要。 英文资料 > 中文资料
- 验证知识点正确性
- 掌握汇编语言是验证知识点正确性的方式。
汇编语言 与 机器语言一一对应,但是汇编语言与高级语言不一定一一对应。
基本语法
常量
-
宏常量:
#define 常量名 常量值
通常在文件上方定义.
-
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字符
字符串类型
表示一串字符
- C风格的字符串:
char 变量名[] - "字符串值";
- C++风格字符串:
string 变量名 = "字符串值"
, 引入头文件<string>
布尔类型
只占用一个字节
- true:本质是0意外的任何数
- false:本质是0
数据的输入
关键字:cin
语法:cin >>
数组
数组特点:
- 放在一块连续的内存空间中
- 数组中的每个元素都是相同的数据类型
一维数组
定义:
数据类型 数组名[数组长度];
数据类型 数组名[数组长度] = {v1, v2,...} ;
数据类型 数组名[] = {v1, v2 ,,,};
一维数组的数组名的用途:
- 可以统计整个数组在内存中的长度:
sizeof(数组名)
- 可以获取数组在内存中的首地址:直接用数组名
arr
或者 数组第一个元素的地址&arr[0]
数组元素互换:
二维数组
定义:
数据类型 数组名[行数][列数];
数据类型 数组名[行数][列数] = {{v11,v12,,} , {v21,v22,,,}, ,,,}
数据类型 数组名[行数][列数] = {v11,v12,, , v21,v22,,,, ,,,}
数据类型 数组名[][] = {v11,v12,, , v21,v22,,,, ,,,}
数组名称的用途:
- 查看所占空间大小
- 获取二维数组的首地址: = 第一行元素的首地址 = 第一行第一个元素的首地址
函数
值传递:形参无论发生什么变化都不影响实参。
**函数的申明:**申明可以写多次
**函数的定义:**定义只能有一次
函数的分文件编写:
- 创建头文件 .h
- 创建源文件 .cpp
- 在头文件中写函数的申明
- 在源文件中写函数的定义,源文件要include 头文件
指针
通过指针保存地址, 指针就是一个地址
定义指针:
- 定义的时候不赋值
- 定义的时候赋值
使用指针:
指针占的数据空间:
32位 4字节,64位8字节。
空指针NULL
- 给指针变量初始化
- 空指针不可以进行访问。(0~255之间的内存编号是系统占用的,不可以访问)
野指针
空指针和野指针都不是申请的地址,尽量避免
const 修饰指针
-
常量指针:
const 数据类型 * 名称
指针的指向可以改,但是指针指向的值不可以改 -
指针常量:
数据类型 * const 名称
指针的指向不可以改,指针指向的值可以改
-
const 既修饰指针又修饰常量:
const 数据类型 * const 名称
结构体
相关概念
用户自定义数据类型
申明:struct 结构体名 {结构体成员列表}
创建:
-
先创建,再赋值(使用
.
访问结构体成员) -
创建的时候直接赋值
struct 结构体名 变量名字 = {结构体成员列表的值}
-
申明的时候创建一个结构体变量,然后再赋值
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
引用
作用:给变量取别名
语法:数据类型 &别名 = 原名
操作别名 跟 操作原名是一样的。
引用的注意事项
- 引用必须要初始化
- 引用在初始化厚不能改变
int a = 10;
int &b = a;
引用作为函数参数
作用:函数传参的时候,可以利用引用的技术让形参修饰实参
有点:可以简化指针修改实参
引用作函数的返回值
注意:
- 不要返回局部变量的引用
- 函数的调用可以作为左值
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
注意事项:
- 如果某个位置有默认参数,那这个位置右边的参数都要有默认参数
- 声明和实现只能有一个有默认参数
函数的占位参数
语法:返回值类型 函数名(数据类型) {}
// 重载++后置运算符的
complex operator++(int);
形参列表中有占位参数,调用的时候就必须填补该参数
占位参数还可以有默认参数
函数重载
**作用:**函数名可以相同,提高复用性。
重载满足条件:
- 同一个作用域下
- 函数名相同
- 函数参数类型不同 、个数不同 或者顺序不同
函数的返回值不能满足重载的条件
注意事项:
-
引用可以作为重载条件
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); }
-
重载遇到默认参数:容易导致歧义
// 会有歧义 void func(int a, int b = 1){} void func(int a){}
-
函数重载的实质:编译器会将函数名和参数类型[包括个数和顺序]一起组合成一个新的函数名字进行区分和调用[name mangling 或者 name decoration 技术]。
类和对象
访问权限
- public:类 内外都可以访问
- protected:类内可以访问,内外不可以。子类可以访问父类
- 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; // 拷贝构造
注意事项:
- 匿名对象:
Person(10)
,当前行执行结束的时候,系统会立即回收匿名对象 - 不要利用拷贝函数初始化匿名对象
- 匿名对象:
拷贝构造函数调用时机
-
使用一个创建完毕的对象来初始一个新对象
-
值传递的方式给函数参数传值
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个构造函数
- 默认构造函数
- 默认析构函数
- 默认拷贝函数
调用规则如下:
- 如果有自定义有参构造函数,c++不会提供无参构造函数
- 如果有自定义拷贝构造函数,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[+ - * / % ......]
- 使用成员函数进行重载
#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));
}
-
使用全局函数进行重载
complex operator+(const complex& c1, const complex& c2) { return complex(c1.real + c.real, c1.img + c.img); }
-
重载
<<
运算符(有点类似于java 中的 toString方法)// 只有这样写 才可以是 cout << complex << ... // 而且要链式编程 ostream& operator<<(ostream& cout, complex c);
// ostream& operator<<(ostream& cout, complex c) { cout << "(" << c.real << ", " << c.img << ")"; return cout; }
-
重载
++
运算符// 重载前置++ 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; }
-
重载赋值
=
运算符C++默认至少给一个类添加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; }
-
重载关系运算符
== >= <= > < !=
用作比较器? -
重载函数调用运算符
()
- 由于重载后使用的方式非常像函数的调用,因此也可以成为仿函数
- 仿函数没有固定的写法,非常灵活
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 | 二进制 |
文本文件——写
步骤:
-
包含头文件
#include <fstream>
-
创建流对象
ofstream ofs;
-
打开文件
ofs.open("path", 打开方式);
如果混用方式打开文件,利用
|
操作符ios::binary | ios::out
-
写数据
ofs << "写入的数据" ;
-
关闭文件
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();
}
文本文件——读
步骤:
- 包含头文件
- 创建流对象
- 打开文件
- 读取文件
- 关闭文件
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位置插入一个eleminsert(pos , n, elem)
:pos是一个迭代器,在pos位置插入n个eleminsert(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
,所以需要增加一个判断,就能无差别切换。
避免头文件重复编译
-
头文件里面加上代码
#pragma once
,就可以了。(要去编译器比较新) -
手动的给每一个头文件定义一个独一无二的宏,再加上一些判断。(对编译器版本没有要求)
#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
,所以需要增加一个判断,就能无差别切换。
避免头文件重复编译
-
头文件里面加上代码
#pragma once
,就可以了。(要去编译器比较新) -
手动的给每一个头文件定义一个独一无二的宏,再加上一些判断。(对编译器版本没有要求)
#ifndef MATH_UTILS_ #define MATH_UTILS_ // 中间是所有的声明代码 #endif
内联函数 和 宏
内联函数 不需要 调用函数的开销。所以开销小,速度快。
不超过十行的代码,经常被调用,就把这个行数改为内联函数。
tips:将vs弄成release 模式,然后vs设置打开优化。