CPP refresh 内存模型 代码区 存放函数体的二进制代码,由操作系统进行管理
全局区 存放全局变量和静态变量以及常量.
栈区
由编译器自动分配释放
存放函数的参数值
存放局部变量
堆区 由程序员分配和释放,若不手动释放,最后将由操作系统回收
程序运行前 程序编译后,生成 exe 可执行程序,未执行该程序前分为两个区域
代码区:
存放 CPU 执行的机器指令
代码区是共享的,共享的的目的是对于频繁被执行的程序,只需要在内存中由一份代码即可
代码区为 只读 ,防止程序意外地修改了它的指令
全局区
全局变量和静态变量存放在此
常量区
字符串
其他常量
==该区域的数据在程序结束后由操作系统释放==
程序运行后 栈区:由编译器自动分配释放,存放函数参数值,局部变量
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
new 操作符 CPP 利用 new 操作符在堆区开辟数据
语法:new 数据类型
==利用 new 创建的数据,会返回该数据对应的类型的指针==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std ;int * func () { int * a = new int (10 ); return a; } int main (int argc, char ** argv) { int *p = func(); cout << *p << endl ; cout << *p << endl ; cout << *p << endl ; delete p; return 0 ; }
开辟数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int main () { int * arr = new int [10 ]; for (int i = 0 ; i < 10 ; i++) { arr[i] = i + 100 ; } for (int i = 0 ; i < 10 ; i++) { cout << arr[i] << endl ; } delete [] arr; return 0 ; }
引用 引用是可以作为函数返回值存在的
注意不要返回局部变量引用
函数变量可以作为左值,则必须为返回引用类型(指针)
引用可以作为函数的返回值存在 ⚠️:不要在函数里返回局部变量
如果函数作为左值,则此被作为左值的函数必须返回为引用类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> using namespace std ;int & quoteTest () { static int a = 10 ; return a; } int main (int argc, char ** argv) { int & ret = quoteTest(); cout <<"a: " << ret<<endl ; cout <<"a: " << ret<<endl ; quoteTest() = 1000 ; cout <<"modified ret: " << ret<<endl ; cout <<"modified ret: " << ret<<endl ; return 0 ; }
引用的本质 本质:引用的本质在 C++ 内部实现是一个指针常量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void func (int & ref) { ref = 100 ; } int main () { int a = 10 ; int & ref = a; ref = 20 ; cout << "a:" << a << endl ; cout << "ref:" << ref << endl ; func(a); return 0 ; }
常量引用 ==常量引用主要用来修饰形参,防止误操作==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> using namespace std ;void showValue (const int & val) { cout << val << endl ; } int main (int argc, char ** argv) { int temp = 10 ; const int & test = temp; showValue(temp); showValue(test); return 0 ; }
函数提高 函数占位参数示例 1 2 3 4 5 6 7 8 void func (int a, int ) { cout <<"pass" <<; } int main (int argc, char **argv) { func(10 , 10 ); returen 0 ; }
函数重载 函数名字可以相同,提高复用性。
重载满足条件:
同一个作用域下
函数名称相同
函数的参数类型不同,或者个数不同,或者顺序不同
注意:函数的返回值不可以作为函数重载的条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 void func () { cout << "func 的调用!" << endl ; } void func (int a) { cout << "func (int a) 的调用!" << endl ; } void func (double a) { cout << "func (double a)的调用!" << endl ; } void func (int a ,double b) { cout << "func (int a ,double b) 的调用!" << endl ; } void func (double a ,int b) { cout << "func (double a ,int b)的调用!" << endl ; } int main () { func(); func(10 ); func(3.14 ); func(10 ,3.14 ); func(3.14 , 10 ); system("pause" ); return 0 ; }
函数重载注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 void func (int &a) { cout << "func (int &a)" << endl ; } void func (const int &a) { cout << "func(const int &a)" << endl ; } void funcc (int a, int b = 10 ) { cout << "..." << endl ; } void funcc (int a ) { cout << "..." << endl ; } int main () { int a = 10 ; func(a); func(10 ); return 0 ; }
类和对象 ==封装、继承、多态==
设计一个圆,求圆的周长
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const double PI = 3.1415926535 ;class Circle { public : int radius; double calculatePT () { return 2 * PI * radius; } }; int main () { Circle cl; cl.radius = 10 ; cout << "perimeter is: " << cl.calculatePT()<<endl ; }
设计一个学生类,有姓名和学号,可以给姓名和学号赋值,显示学生姓名和学号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Student { public : void setName (String name) { this ->name = name; } void setId (int id) { this ->id = id; } void showStudent () { cout << "name: " << name << "id: " << id<<endl ; } public : String name; int id; }; int main () { Student stu; stu.setName("nike" ); stu.setId(123 ); stu.showStudent(); return 0 ; }
封装权限: public :公共权限,类内可以访问,类外也可以访问
protected:保护权限,类内可以访问,类外不可以访问
private:私有权限,类内可以访问,类外不可以访问
struct 与 class 区别 二者的唯一区别在于默认的访问权限的不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class C1 { int m_A; }; struct C2 { int m_A; }; int main () { C1 c1; c1.m_A = 10 ; C2 c2; c2.m_A = 10 ; system("pause" ); return 0 ; }
对象的初始化和清理 C++ 利用构造函数和析构函数解决对象的初始化和清理两个重要问题。
如果不手动提供构造函数和析构函数,编译器会提供
构造函数语法:类名(){}
构造函数,没有返回值,不写void
函数名称与类名称相同
构造函数可以有参数,可以发生重载
程序在调用对象时候会自动调用构造,且只调用一次
析构函数语法:~类名(){}
析构函数没有返回值不写void
函数名与类名相同,前加 ~
析构函数不可以有参数,不发生重载
程序在对象销毁前自动调用析构,无需手动调用,且只调用一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Person { public : Person() { cout << "Person的构造函数调用" << endl ; } ~Person() { cout << "Person的析构函数调用" << endl ; } }; void test01 () { Person p; } int main () { test01(); return 0 ; }
构造函数中的拷贝构造函数 1 2 3 4 5 6 7 8 9 10 class Person { public : Person(const Person& p) { age = p.age; cout << "拷贝构造函数" << endl ; } public : int age; };
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class Person {public : Person() { cout << "无参构造函数!" << endl ; } Person(int a) { age = a; cout << "有参构造函数!" << endl ; } Person(const Person& p) { age = p.age; cout << "拷贝构造函数!" << endl ; } ~Person() { cout << "析构函数!" << endl ; } public : int age; }; void test01 () { Person p; } void test02 () { Person p1 (10 ) ; Person p2 = Person(10 ); Person p3 = Person(p2); Person(10 ) Person p4 = 10 ; Person p5 = p4; } int main () { test01(); system("pause" ); return 0 ; }
拷贝构造函数调用时机
使用一个已经创建完毕的对象来初始化一个新对象
值传递的方式给函数参数传值
1 2 3 4 5 6 7 8 9 void doWork (Person p1) {} void test02 () { Person p; doWork(p); }
以值方式返回局部对象
1 2 3 4 5 6 7 8 9 10 11 12 13 Person doWork2 () { Person p1; cout << (int *)&p1 << endl ; return p1; } void test03 () { Person p = doWork2(); cout << (int *)&p << endl ; }
构造函数调用规则 默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
深拷贝与浅拷贝 浅拷贝:简单的赋值拷贝
深拷贝:在堆区重新申请空间,进行拷贝操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class Person {public : Person() { cout << "无参构造函数!" << endl ; } Person(int age ,int height ) { cout << "有参构造函数!" << endl ; m_age = age; m_height = new int (height ); } Person(const Person& p) { cout << "拷贝构造函数!" << endl ; m_age = p.m_age; m_height = new int (*p.m_height); } ~Person() { cout << "析构函数!" << endl ; if (m_height != NULL ) { delete m_height; } } public : int m_age; int * m_height; }; void test01 () { Person p1 (18 , 180 ) ; Person p2 (p1) ; cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl ; cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl ; } int main () { test01(); system("pause" ); return 0 ; }
总结:如果属性有在 堆区 开辟的,一定要自己提供拷贝函数,防止浅拷贝带来的问题
初始化列表 1 2 3 4 5 6 7 8 9 10 11 12 Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {} void PrintPerson () { cout << "mA:" << m_A << endl ; cout << "mB:" << m_B << endl ; cout << "mC:" << m_C << endl ; } private : int m_A; int m_B; int m_C; };
类对象作为类成员 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class Phone { public : Phone(string name) { m_PhoneName = name; cout << "Phone构造" << endl ; } ~Phone() { cout << "Phone析构" << endl ; } string m_PhoneName; }; class Person { public : Person(string name, string pName) :m_Name(name), m_Phone(pName) { cout << "Person构造" << endl ; } ~Person() { cout << "Person析构" << endl ; } void playGame () { cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl ; } string m_Name; Phone m_Phone; }; void test01 () { Person p ("张三" , "苹果X" ) ; p.playGame(); } int main () { test01(); system("pause" ); return 0 ; }
//初始化列表可以告诉编译器调用哪一个构造函数
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person构造" << endl;
}
Phone m_Phone;
以上两处,构造函数中 pName 为 string 类型, m_Phone 为 Phone 对象类型,其实是构造函数的隐式调用
// Phone m_Phone = pName;
等价于以下:
// Phone m_Phone = Phone(pName);
静态成员 静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
静态成员变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Person { public : static int m_A; private : static int m_B; }; int Person::m_A = 10 ;int Person::m_B = 10 ;void test01 () { Person p1; p1.m_A = 100 ; cout << "p1.m_A = " << p1.m_A << endl ; Person p2; p2.m_A = 200 ; cout << "p1.m_A = " << p1.m_A << endl ; cout << "p2.m_A = " << p2.m_A << endl ; cout << "m_A = " << Person::m_A << endl ; } int main () { test01(); system("pause" ); return 0 ; }
静态成员函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class Person { public : static void func () { cout << "func调用" << endl ; m_A = 100 ; } static int m_A; int m_B; private : static void func2 () { cout << "func2调用" << endl ; } }; int Person::m_A = 10 ;void test01 () { Person p1; p1.func(); Person::func(); } int main () { test01(); system("pause" ); return 0 ; }
this 指针指向被调用的成员函数所属的对象 this 指针的用途
==当形参和变量名相同时,可用 this 来进行区分,其指向类所有的成员变量==
==在类的非静态成员函数中返回对象本身,可以使用 return *this==`
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std ;class Person { public : Person(int age) { this ->age = age; } Person& PersonAddPerson (Person p) { this ->age += p.age; return *this ; } public : int age; }; void test01 () { Person p1 (10 ) ; cout << "p1.age= " << p1.age << endl ; Person p2 (10 ) ; p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1); cout << "after add, the value of p2= " << p2.age << endl ; } int main () { test01(); system("pause" ); return 0 ; }
空指针访问成员函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Person {public : void ShowClassName () { cout << "我是Person类!" << endl ; } void ShowPerson () { if (this == NULL ) { return ; } cout << mAge << endl ; } public : int mAge; }; void test01 () { Person * p = NULL ; p->ShowClassName(); p->ShowPerson(); } int main () { test01(); system("pause" ); return 0 ; }
const 修饰成员函数 常函数
按理说在设计类之前,就应该预先设置好一个函数是否为常函数。
成员函数后加 const 即为常函数
常函数内不可以进行修改成员属性
==成员属性声明时加关键字 mutable 后,常函数中依然可以进行修改==
常对象
声明对象前加 const 称该对象为常对象
常对象只能调用常函数
友元 友元的关键字为 ==friend==
同一个 class 生成的 object 互为友元。
友元的三种实现
1 2 3 4 5 6 7 8 9 10 11 12 class Building { friend void goodGay (Building * building) ; public : Building() { this ->m_SittingRoom = "客厅" ; this ->m_BedRoom = "卧室" ; }
1 2 3 4 5 6 7 8 9 10 11 12 public : string m_SittingRoom; private : string m_BedRoom; }; void goodGay (Building * building) { cout << "好基友正在访问: " << building->m_SittingRoom << endl ; cout << "好基友正在访问: " << building->m_BedRoom << endl ; }
1 2 3 4 5 6 7 8 9 10 11 12 void test01 () {Building b; goodGay(&b); } int main () {test01(); system("pause" ); return 0 ;}
类做友元1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 将一个类作为另一个类的友元,即可以访问类中的私有成员。 ```c++ class Building; class goodGay { public: goodGay(); void visit(); private: Building *building; }; class Building { //告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容 friend class goodGay; public: Building(); public: string m_SittingRoom; //客厅 private: string m_BedRoom;//卧室 }; // 在类外写成员函数 Building::Building() { this->m_SittingRoom = "客厅"; this->m_BedRoom = "卧室"; } goodGay::goodGay() { building = new Building; } void goodGay::visit() { cout << "好基友正在访问" << building->m_SittingRoom << endl; cout << "好基友正在访问" << building->m_BedRoom << endl; } void test01() { goodGay gg; gg.visit(); } int main(){ test01(); system("pause"); return 0; }
class Building
{
//告诉编译器 goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
friend void goodGay::visit();
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom;//卧室
};
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void goodGay::visit2()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
//cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}
友元总结:成为其他类或函数的朋友,就可以访问其私有成员。
操作符重载 C++中操作符就是一种函数。
加号运算符重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class Person {public : Person() {}; Person(int a, int b) { this ->m_A = a; this ->m_B = b; } Person operator +(const Person& p) { Person temp; temp.m_A = this ->m_A + p.m_A; temp.m_B = this ->m_B + p.m_B; return temp; } public : int m_A; int m_B; }; Person operator +(const Person& p2, int val) { Person temp; temp.m_A = p2.m_A + val; temp.m_B = p2.m_B + val; return temp; } void test () { Person p1 (10 , 10 ) ; Person p2 (20 , 20 ) ; Person p3 = p2 + p1; cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl ; Person p4 = p3 + 10 ; cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl ; } int main () { test(); system("pause" ); return 0 ; }
对于内置的数据类型的表达式的运算符不能被改变,如 int, double, float 但是自定义的是可以进行更改的。
左移运算符 << 重载 通常不会利用成员函数重载 << 运算符,因为无法实现 cout 在左侧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Person { friend ostream& operator <<(ostream& out, Person& p); public : Person(int a, int b) { this ->m_A = a; this ->m_B = b; } private : int m_A; int m_B; }; ostream& operator <<(ostream& cout , Person& p) { cout << "a:" << p.m_A << " b:" << p.m_B; return cout ; } void test () { Person p1 (10 , 20 ) ; cout << p1 << "hello world" << endl ; } int main () { test(); system("pause" ); return 0 ; }
递增运算符重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include <iostream> using namespace std ;class MyInteger { friend ostream& operator <<(ostream& out, MyInteger myint); public : MyInteger() { m_Num = 0 ; } MyInteger& operator ++() { m_Num++; return *this ; } MyInteger& operator --() { m_Num--; return *this ; } MyInteger operator ++(int ) { MyInteger temp = *this ; m_Num++; return temp; } MyInteger operator --(int ) { MyInteger temp = *this ; m_Num--; return temp; } private : int m_Num; }; ostream& operator <<(ostream& out, MyInteger myint) { out << myint.m_Num; return out; } void test01 () { MyInteger myInt; cout << ++myInt << endl ; cout << myInt << endl ; } void test02 () { MyInteger myInt; cout << myInt++ << endl ; cout << myInt << endl ; } void test03 () { MyInteger myInt; cout << myInt-- << endl ; } void test04 () { MyInteger myInt; cout << --myInt << endl ; } int main () { test04(); system("pause" ); return 0 ; }
前置递增返回的是引用,后置递增返回的是值(由于提前 return 会结束当前函数,所以后置递增时,先用 temp 接受当前值,然后递增完,返回 temp 的值)
赋值运算符(=)的重载 C++ 编译器至少给一个类添加4个函数
默认构造函数(无参数,函数体为空)
默认析构函数(无参数,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
赋值运算符 operator= ,对属性进行值拷贝
问题所在:
解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 class Person { public : Person(int age) { m_Age = new int (age); } Person& operator =(Person &p) { if (m_Age != NULL ) { delete m_Age; m_Age = NULL ; } m_Age = new int (*p.m_Age); return *this ; } ~Person() { if (m_Age != NULL ) { delete m_Age; m_Age = NULL ; } } int *m_Age; }; void test01 () { Person p1 (18 ) ; Person p2 (20 ) ; Person p3 (30 ) ; p3 = p2 = p1; cout << "p1的年龄为:" << *p1.m_Age << endl ; cout << "p2的年龄为:" << *p2.m_Age << endl ; cout << "p3的年龄为:" << *p3.m_Age << endl ; } int main () { test01(); system("pause" ); return 0 ; }
关系运算符(==)重载 作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 class Person { public : Person(string name, int age) { this ->m_Name = name; this ->m_Age = age; }; bool operator ==(Person & p) { if (this ->m_Name == p.m_Name && this ->m_Age == p.m_Age) { return true ; } else { return false ; } } bool operator !=(Person & p) { if (this ->m_Name == p.m_Name && this ->m_Age == p.m_Age) { return false ; } else { return true ; } } string m_Name; int m_Age; }; void test01 () { Person a ("孙悟空" , 18 ) ; Person b ("孙悟空" , 18 ) ; if (a == b) { cout << "a和b相等" << endl ; } else { cout << "a和b不相等" << endl ; } if (a != b) { cout << "a和b不相等" << endl ; } else { cout << "a和b相等" << endl ; } } int main () { test01(); system("pause" ); return 0 ; }
函数调用运算符 () 重载
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数调用,因此称为仿函数
仿函数没有固定写法,非常灵活
即为对一个类的对象后使用()进行类似于函数式的调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class MyPrint { public : void operator () (string text ) { cout << text << endl ; } }; void test01 () { MyPrint myFunc; myFunc("hello world" ); } class MyAdd { public : int operator () (int v1, int v2) { return v1 + v2; } }; void test02 () { MyAdd add; int ret = add(10 , 10 ); cout << "ret = " << ret << endl ; cout << "MyAdd()(100,100) = " << MyAdd()(100 , 100 ) << endl ; } int main () { test01(); test02(); system("pause" ); return 0 ; }
继承 语法: class
子类
:
继承方式
父类
子类:派生类
父类:基类
权限:公共、保护、私有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 class Base1 { public : int m_A; protected : int m_B; private : int m_C; }; class Son1 :public Base1{ public : void func () { m_A; m_B; } }; void myClass () { Son1 s1; s1.m_A; } class Base2 { public : int m_A; protected : int m_B; private : int m_C; }; class Son2 :protected Base2{ public : void func () { m_A; m_B; } }; void myClass2 () { Son2 s; } class Base3 { public : int m_A; protected : int m_B; private : int m_C; }; class Son3 :private Base3{ public : void func () { m_A; m_B; } }; class GrandSon3 :public Son3{ public : void func () { } };
继承中的对象模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Base { public : int m_A; protected : int m_B; private : int m_C; }; class Son :public Base{ public : int m_D; }; void test01 () { cout << "sizeof Son = " << sizeof (Son) << endl ; } int main () { test01(); system("pause" ); return 0 ; }
父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
继承中构造和析构顺序
继承中同名成员的处理方式
访问子类同名成员,直接访问
访问父类同名成员,需要加作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class Base {public : Base() { m_A = 100 ; } void func () { cout << "Base - func()调用" << endl ; } void func (int a) { cout << "Base - func(int a)调用" << endl ; } public : int m_A; }; class Son : public Base {public : Son() { m_A = 200 ; } void func () { cout << "Son - func()调用" << endl ; } public : int m_A; }; void test01 () { Son s; cout << "Son下的m_A = " << s.m_A << endl ; cout << "Base下的m_A = " << s.Base::m_A << endl ; s.func(); s.Base::func(); s.Base::func(10 ); } int main () { test01(); system("pause" ); return EXIT_SUCCESS; }
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
总结:想要访问父类中被重复的对象,添加作用域才可访问到。
继承同名静态成员处理方式
访问子类同名成员,直接访问即可
访问父类同名成员,需要天机啊作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 class Base {public : static void func () { cout << "Base - static void func()" << endl ; } static void func (int a) { cout << "Base - static void func(int a)" << endl ; } static int m_A; }; int Base::m_A = 100 ;class Son : public Base {public : static void func () { cout << "Son - static void func()" << endl ; } static int m_A; }; int Son::m_A = 200 ;void test01 () { cout << "通过对象访问: " << endl ; Son s; cout << "Son 下 m_A = " << s.m_A << endl ; cout << "Base 下 m_A = " << s.Base::m_A << endl ; cout << "通过类名访问: " << endl ; cout << "Son 下 m_A = " << Son::m_A << endl ; cout << "Base 下 m_A = " << Son::Base::m_A << endl ; } void test02 () { cout << "通过对象访问: " << endl ; Son s; s.func(); s.Base::func(); cout << "通过类名访问: " << endl ; Son::func(); Son::Base::func(); Son::Base::func(100 ); } int main () { test02(); system("pause" ); return 0 ; }
多继承语法 C++ 允许一个类继承多个类
语法: class
子类
:继承方式
父类1
, 继承方式
父类2
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++ 实际开发中不建议用多继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class Base1 {public : Base1() { m_A = 100 ; } public : int m_A; }; class Base2 {public : Base2() { m_A = 200 ; } public : int m_A; }; class Son : public Base2, public Base1{ public : Son() { m_C = 300 ; m_D = 400 ; } public : int m_C; int m_D; }; void test01 () { Son s; cout << "sizeof Son = " << sizeof (s) << endl ; cout << s.Base1::m_A << endl ; cout << s.Base2::m_A << endl ; } int main () { test01(); system("pause" ); return 0 ; }
菱形继承问题的解决方式 菱形继承即,多继承了父类的相同同名属性,带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
在继承之前,添加关键字 virtual
变为虚继承
class Sheep : virtual public Animal{};
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Animal { public : int m_Age; }; class Sheep : virtual public Animal {};class Tuo : virtual public Animal {};class SheepTuo : public Sheep, public Tuo {};void test01 () { SheepTuo st; st.Sheep::m_Age = 100 ; st.Tuo::m_Age = 200 ; cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl ; cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl ; cout << "st.m_Age = " << st.m_Age << endl ; } int main () { test01(); system("pause" ); return 0 ; }
多态
==动态多态: 派生类和虚函数实现运行时多态==
函数地址在运行阶段才能确定,就是动态联编
静态多态和动态多态区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
(动态地址通过在父类函数中使用 virtual 来进行虚函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class Animal { public : virtual void speak () { cout << "动物在说话" << endl ; } }; class Cat :public Animal{ public : void speak () { cout << "小猫在说话" << endl ; } }; class Dog :public Animal{ public : void speak () { cout << "小狗在说话" << endl ; } }; void DoSpeak (Animal & animal) { animal.speak(); } void test01 () { Cat cat; DoSpeak(cat); Dog dog; DoSpeak(dog); } int main () { test01(); system("pause" ); return 0 ; }
纯虚函数和抽象类 父类中的虚函数实现大多数时候是毫无意义的,通常都是利用子类中进行调用子类的重写的内容。
子类必须重写父类中的纯虚函数(Java接口同)
当类中有了纯虚函数,那么这个类就称为==抽象类==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Base { public : virtual void func () = 0 ; }; class Son :public Base{ public : virtual void func () { cout << "func调用" << endl ; }; }; void test01 () { Base * base = NULL ; base = new Son; base->func(); delete base; } int main () { test01(); system("pause" ); return 0 ; }
多态案例,制作饮品 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include <iostream> using namespace std ;class Abstractdrinking {public : virtual void HeatWater () = 0 ; virtual void Brew () = 0 ; virtual void PourIn () = 0 ; virtual void AddSup () = 0 ; void MakeDrinking () { HeatWater(); Brew(); PourIn(); AddSup(); } }; class Coffee : public Abstractdrinking{ virtual void HeatWater () { cout <<"Heat NongF Water." <<endl ; } virtual void Brew () { cout <<"brew coffee." <<endl ; } virtual void PourIn () { cout <<"Pour in NongF Water for Coffee." <<endl ; } virtual void AddSup () { cout <<"Add support milk & sugar." <<endl ; } }; class Tea : public Abstractdrinking{ virtual void HeatWater () { cout <<"Heat Quan Water." <<endl ; } virtual void Brew () { cout <<"brew tea." <<endl ; } virtual void PourIn () { cout <<"Pour in Quan Water for tea." <<endl ; } virtual void AddSup () { cout <<"Add support lemon." <<endl ; } }; void doWork (Abstractdrinking &abstractdrinking) { abstractdrinking.MakeDrinking(); } void doWork (Abstractdrinking *abstractdrinking) { abstractdrinking->MakeDrinking(); delete abstractdrinking; abstractdrinking = nullptr ; } void test01 () { Abstractdrinking *coffee = new Coffee(); doWork(*coffee); delete coffee; coffee = nullptr ; cout <<"---------------------------------" <<endl ; Abstractdrinking *tea = new Tea(); doWork(*tea); delete tea; tea = nullptr ; } void test02 () { cout <<"doWork with test02 using point." <<endl ; doWork(new Coffee()); cout <<"---------------------------------" <<endl ; doWork(new Tea()); } int main () { test01(); cout <<"=================================" <<endl ; test02(); return 0 ; }
虚析构和纯虚析构 父类指针在析构的时候,不会调用子类中析构函数,导致子类如果有堆区属性,会出现内存泄漏的情况
利用虚析构可以解决父类指针释放子类对象时不干净的问题
virtual ~Animal(){cout<<"destruct"<<endl;}
==纯虚析构需要声明也需要实现==
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构区别:
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名()=0
类名::~类名(){}
(类内声明,类外实现)
总结:
1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类(抽象类无法实例化对象)
多态案例:电脑组装 案例描述:
电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 #include <iostream> using namespace std ;class CPU { public : virtual void calculate () = 0 ; }; class VideoCard { public : virtual void display () = 0 ; }; class Memory { public : virtual void storage () = 0 ; }; class Computer { public : Computer(CPU * cpu, VideoCard * vc, Memory * mem) { m_cpu = cpu; m_vc = vc; m_mem = mem; } void work () { m_cpu->calculate(); m_vc->display (); m_mem->storage(); } ~Computer() { if (m_cpu != NULL ) { delete m_cpu; m_cpu = NULL ; } if (m_vc != NULL ) { delete m_vc; m_vc = NULL ; } if (m_mem != NULL ) { delete m_mem; m_mem = NULL ; } } private : CPU * m_cpu; VideoCard * m_vc; Memory * m_mem; }; class IntelCPU :public CPU{ public : virtual void calculate () { cout << "Intel的CPU开始计算了!" << endl ; } }; class IntelVideoCard :public VideoCard{ public : virtual void display () { cout << "Intel的显卡开始显示了!" << endl ; } }; class IntelMemory :public Memory{ public : virtual void storage () { cout << "Intel的内存条开始存储了!" << endl ; } }; class LenovoCPU :public CPU{ public : virtual void calculate () { cout << "Lenovo的CPU开始计算了!" << endl ; } }; class LenovoVideoCard :public VideoCard{ public : virtual void display () { cout << "Lenovo的显卡开始显示了!" << endl ; } }; class LenovoMemory :public Memory{ public : virtual void storage () { cout << "Lenovo的内存条开始存储了!" << endl ; } }; void test01 () { CPU * intelCpu = new IntelCPU; VideoCard * intelCard = new IntelVideoCard; Memory * intelMem = new IntelMemory; cout << "第一台电脑开始工作:" << endl ; Computer * computer1 = new Computer(intelCpu, intelCard, intelMem); computer1->work(); delete computer1; cout << "-----------------------" << endl ; cout << "第二台电脑开始工作:" << endl ; Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);; computer2->work(); delete computer2; cout << "-----------------------" << endl ; cout << "第三台电脑开始工作:" << endl ; Computer * computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);; computer3->work(); delete computer3; }
文件操作 文件类型:
文本文件 - 以文本的 ASCII 码形式存储在计算机中
以二进制形式存储在计算机中,一般用户不能直接读懂
==包含头文件 #include\==
操作文件的三大类:
ofstream:写操作
ifstream:读操作
fstream:读写操作
打开方式
解释
ios::in
为读文件而打开文件
ios::out
为写文件而打开文件
ios::ate
初始位置:文件尾
ios::app
追加方式写文件
ios::trunc
如果文件存在先删除,再创建
ios::binary
二进制方式
利用 | 操作符可以设置二进制方式写文件 ios::binary | ios::out
写文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <fstream> void test01 () { ofstream ofs; ofs.open ("test.txt" , ios::out); ofs << "姓名:张三" << endl ; ofs << "性别:男" << endl ; ofs << "年龄:18" << endl ; ofs.close (); } int main () { test01(); system("pause" ); return 0 ; }
文件操作必须包含头文件 fstream
读文件可以利用 ofstream ,或者fstream类
打开文件时候需要指定操作文件的路径,以及打开方式
利用<<可以向文件中写数据
操作完毕,要关闭文件
读文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <fstream> #include <string> void test01 () { ifstream ifs; ifs.open ("test.txt" , ios::in); if (!ifs.is_open()) { cout << "文件打开失败" << endl ; return ; } char c; while ((c = ifs.get ()) != EOF) { cout << c; } ifs.close (); } int main () { test01(); system("pause" ); return 0 ; }
is_open 可以判断文件是否打开成功
二进制文件 写文件
函数原型:
ostream& write(const char * buffer, int len);
buffer 指向内存中一段存储空间,len 是读写的字节数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <fstream> #include <string> class Person { public : char m_Name[64 ]; int m_Age; }; void test01 () { ofstream ofs ("person.txt" , ios::out | ios::binary) ; Person p = {"张三" , 18 }; ofs.write ((const char *)&p, sizeof (p)); ofs.close (); } int main () { test01(); system("pause" ); return 0 ; }
读文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <fstream> #include <string> class Person { public : char m_Name[64 ]; int m_Age; }; void test01 () { ifstream ifs ("person.txt" , ios::in | ios::binary) ; if (!ifs.is_open()) { cout << "文件打开失败" << endl ; } Person p; ifs.read ((char *)&p, sizeof (p)); cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl ; } int main () { test01(); system("pause" ); return 0 ; }
C++ 泛型概念(模板) 模板特点:
模板不可以直接使用,它作为一个框架
模板不是万能通用的
函数模板和类模板
函数模板语法 函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型 来代表。
语法:
1 2 template <typename T>函数声明或定义
解释:
template —- 声明创建模板
typename —- 表面其后面的符号是一种数据类型,可以用class代替(即为类模板)
T —- 通用的数据类型,名称可以替换,通常为大写字母
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 void swapInt (int & a, int & b) { int temp = a; a = b; b = temp; } void swapDouble (double & a, double & b) { double temp = a; a = b; b = temp; } template <typename T>void mySwap (T& a, T& b) { T temp = a; a = b; b = temp; } void test01 () { int a = 10 ; int b = 20 ; mySwap(a, b); mySwap<int >(a, b); cout << "a = " << a << endl ; cout << "b = " << b << endl ; } int main () { test01(); system("pause" ); return 0 ; }
总结:
函数模板注意事项 注意事项:
自动类型推导,必须推导出一致的数据类型T,才可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 template <class T >void mySwap (T & a , T & b ){ T temp = a; a = b; b = temp; } void test01 () { int a = 10 ; int b = 20 ; char c = 'c' ; mySwap(a, b); } template <class T >void func (){ cout << "func 调用" << endl ; } void test02 () { func<int >(); } int main () { test01(); test02(); system("pause" ); return 0 ; }
使用模板时必须确定出通用数据类型 T ,并且能够推导出一致的类型
函数模板案例:选择排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 template <typename T>void mySwap (T &a, T &b) { T temp = a; a = b; b = temp; } template <class T >void mySort (T arr [], T len ){ for (int i = 0 ; i < len; i++) { int max = i; for (int j = i+1 ; j < len; j++){ if (arr[j] > max ){ max = j; } } if (max != i){ mySwap(arr[max ], arr[i]); } } } template <typename T>void printArray (T arr[], int len) { for (int i = 0 ; i < len; i++) { cout << arr[i] << " " ; } cout << endl ; } void test01 () { char charArr[] = "bdcfeagh" ; mySort(charArr, sizeof (charArr)/sizeof (char ) ); printArray(charArr, sizeof (charArr)/sizeof (char )); } void test02 () { int intArr[] = { 7 , 5 , 8 , 1 , 3 , 9 , 2 , 4 , 6 }; int num = sizeof (intArr) / sizeof (int ); mySort(intArr, num); printArray(intArr, num); } int main () { test01(); test02(); return 0 ; }
普通函数与函数模板的区别 普通函数与函数模板区别:
普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
如果利用显示指定类型的方式,可以发生隐式类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 int myAdd01 (int a, int b) { return a + b; } template <class T >T myAdd02 (T a , T b ){ return a + b; } void test01 () { int a = 10 ; int b = 20 ; char c = 'c' ; cout << myAdd01(a, c) << endl ; myAdd02<int >(a, c); } int main () { test01(); system("pause" ); return 0 ; }
模板的局限性 局限性在于:对于模板中的 T 这个数据类型来说,有些时候它推导不出来 T 属于什么类型 ,这是最大的局限性
例如:
1 2 3 4 5 template <class T >void f (T a , T b ){ a = b; }
在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了
再例如:
1 2 3 4 5 template <class T >void f (T a , T b ){ if (a > b) { ... } }
在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行
因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型 提供具体化的模板
具体化的模板
语法:
1 2 3 template <> bool myCompare (Person &p1, Person &p2)
对比于:
1 2 3 4 template <class T >void myTest (int a , int b ){ ... }
类模板 注意:
类模板只能用显示指定类型方式
类模板中模板参数列表可以有默认参数
语法:
1 2 template <typename T>class className { };
在类模板中,声明模板 template 后面加类
类模板与函数模板区别
类模板没有自动类型推导的使用方式
类模板使用只能用显示指定类型方式
类模板在模板参数列表中有默认参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <string> template <class NameType , class AgeType = int > //设置的默认参数 int class Person { public : Person(NameType name, AgeType age) { this ->mName = name; this ->mAge = age; } void showPerson () { cout << "name: " << this ->mName << " age: " << this ->mAge << endl ; } public : NameType mName; AgeType mAge; }; void test01 () { Person <string ,int >p("孙悟空" , 1000 ); p.showPerson(); } void test02 () { Person <string > p("猪八戒" , 999 ); p.showPerson(); } int main () { test01(); test02(); system("pause" ); return 0 ; }
类模板中成员函数创建时机
普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Person1 { public : void showPerson1 () { cout << "Person1 show" << endl ; } }; class Person2 { public : void showPerson2 () { cout << "Person2 show" << endl ; } }; template <class T >class MyClass { public : T obj; void fun1 () { obj.showPerson1(); } void fun2 () { obj.showPerson2(); } }; void test01 () { MyClass<Person1> m; m.fun1(); } int main () { test01(); system("pause" ); return 0 ; }
类模板对象作函数参数
指定传入的类型 —- 直接显示对象的数据类型
参数模板化 —- 将对象中的参数变为模板进行传递
整个类模板化 —- 将这个对象类型 模板化进行传递
其中指定数据类型是使用最广泛的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <string> template <class NameType , class AgeType = int >class Person { public : Person(NameType name, AgeType age) { this ->mName = name; this ->mAge = age; } void showPerson () { cout << "name: " << this ->mName << " age: " << this ->mAge << endl ; } public : NameType mName; AgeType mAge; }; void printPerson1 (Person<string , int > &p) { p.showPerson(); } void test01 () { Person <string , int >p("孙悟空" , 100 ); printPerson1(p); } template <class T1 , class T2 >void printPerson2 (Person <T1, T2>&p ){ p.showPerson(); cout << "T1的类型为: " << typeid (T1).name() << endl ; cout << "T2的类型为: " << typeid (T2).name() << endl ; } void test02 () { Person <string , int >p("猪八戒" , 90 ); printPerson2(p); } template <class T >void printPerson3 (T & p ){ cout << "T的类型为: " << typeid (T).name() << endl ; p.showPerson(); } void test03 () { Person <string , int >p("唐僧" , 30 ); printPerson3(p); } int main () { test01(); test02(); test03(); system("pause" ); return 0 ; }
类模板与继承
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中 T 的类型
如果不指定,编译器无法给子类分配内存
如果想灵活指定出父类中T的类型,子类也需变为类模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 template <class T >class Base { T m; }; class Son :public Base<int > { }; void test01 () { Son c; } template <class T1 , class T2 >class Son2 :public Base<T2>{ public : Son2() { cout << typeid (T1).name() << endl ; cout << typeid (T2).name() << endl ; } }; void test02 () { Son2<int , char > child1; } int main () { test01(); test02(); system("pause" ); return 0 ; }
类模板成员函数与类外实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <string> template <class T1 , class T2 >class Person {public : Person(T1 name, T2 age); void showPerson () ; public : T1 m_Name; T2 m_Age; }; template <class T1 , class T2 >Person <T1, T2>: :Person(T1 name, T2 age) { this ->m_Name = name; this ->m_Age = age; } template <class T1 , class T2 >void Person <T1, T2>: :showPerson() { cout << "姓名: " << this ->m_Name << " 年龄:" << this ->m_Age << endl ; } void test01 () { Person<string, int> p("Tom", 20); p.showPerson(); } int main () { test01(); system("pause" ); return 0 ; }
类外实现需要添加模板参数列表
类模板分文件编写 #pragma once防止头文件重复编译,一般写在.h头文件中
问题:
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
解决方式1:直接包含在.cpp源文件
解决方式2:只将类模板的声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
person.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #pragma once #include <iostream> using namespace std ;#include <string> template <class T1 , class T2 >class Person {public : Person(T1 name, T2 age); void showPerson () ; public : T1 m_Name; T2 m_Age; }; template <class T1 , class T2 >Person <T1, T2>: :Person(T1 name, T2 age) { this ->m_Name = name; this ->m_Age = age; } template <class T1 , class T2 >void Person <T1, T2>: :showPerson() { cout << "姓名: " << this ->m_Name << " 年龄:" << this ->m_Age << endl ; }
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> using namespace std ;#include "person.cpp" #include "person.hpp" void test01 () { Person<string, int> p("Tom", 10); p.showPerson(); } int main () { test01(); system("pause" ); return 0 ; }
类模板案例
可以对内置数据类型以及自定义数据类型的数据进行存储
将数组中的数据存储到堆区
构造函数中可以传入数组的容量
提供对应的拷贝构造函数以及operator=防止浅拷贝问题
提供尾插法和尾删法对数组中的数据进行增加和删除
可以通过下标的方式访问数组中的元素
可以获取数组中当前元素个数和数组的容量
myArr.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #pragma once #include <iostream> using namespace std ;template <class T >class MyArray { public : MyArray(int capacity) { this ->m_Capacity = capacity; this ->m_Size = 0 ; pAddress = new T[this ->m_Capacity]; } MyArray(const MyArray & arr) { this ->m_Capacity = arr.m_Capacity; this ->m_Size = arr.m_Size; this ->pAddress = new T[this ->m_Capacity]; for (int i = 0 ; i < this ->m_Size; i++) { this ->pAddress[i] = arr.pAddress[i]; } } MyArray& operator =(const MyArray& myarray) { if (this ->pAddress != NULL ) { delete [] this ->pAddress; this ->m_Capacity = 0 ; this ->m_Size = 0 ; } this ->m_Capacity = myarray.m_Capacity; this ->m_Size = myarray.m_Size; this ->pAddress = new T[this ->m_Capacity]; for (int i = 0 ; i < this ->m_Size; i++) { this ->pAddress[i] = myarray[i]; } return *this ; } T& operator [](int index) { return this ->pAddress[index]; } void Push_back (const T & val) { if (this ->m_Capacity == this ->m_Size) { return ; } this ->pAddress[this ->m_Size] = val; this ->m_Size++; } void Pop_back () { if (this ->m_Size == 0 ) { return ; } this ->m_Size--; } int getCapacity () { return this ->m_Capacity; } int getSize () { return this ->m_Size; } ~MyArray() { if (this ->pAddress != NULL ) { delete [] this ->pAddress; this ->pAddress = NULL ; this ->m_Capacity = 0 ; this ->m_Size = 0 ; } } private : T * pAddress; int m_Capacity; int m_Size; };
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include "myArr.hpp" #include <string> void printIntArray (MyArray<int >& arr) { for (int i = 0 ; i < arr.getSize(); i++) { cout << arr[i] << " " ; } cout << endl ; } void test01 () { MyArray<int > array1 (10 ) ; for (int i = 0 ; i < 10 ; i++) { array1.Push_back(i); } cout << "array1打印输出:" << endl ; printIntArray(array1); cout << "array1的大小:" << array1.getSize() << endl ; cout << "array1的容量:" << array1.getCapacity() << endl ; cout << "--------------------------" << endl ; MyArray<int > array2 (array1) ; array2.Pop_back(); cout << "array2打印输出:" << endl ; printIntArray(array2); cout << "array2的大小:" << array2.getSize() << endl ; cout << "array2的容量:" << array2.getCapacity() << endl ; } class Person {public : Person() {} Person(string name, int age) { this ->m_Name = name; this ->m_Age = age; } public : string m_Name; int m_Age; }; void printPersonArray (MyArray<Person>& personArr) { for (int i = 0 ; i < personArr.getSize(); i++) { cout << "姓名:" << personArr[i].m_Name << " 年龄: " << personArr[i].m_Age << endl ; } } void test02 () { MyArray<Person> pArray (10 ) ; Person p1 ("孙悟空" , 30 ) ; Person p2 ("韩信" , 20 ) ; Person p3 ("妲己" , 18 ) ; Person p4 ("王昭君" , 15 ) ; Person p5 ("赵云" , 24 ) ; pArray.Push_back(p1); pArray.Push_back(p2); pArray.Push_back(p3); pArray.Push_back(p4); pArray.Push_back(p5); printPersonArray(pArray); cout << "pArray的大小:" << pArray.getSize() << endl ; cout << "pArray的容量:" << pArray.getCapacity() << endl ; } int main () { test02(); system("pause" ); return 0 ; }
STL
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
算法:各种常用的算法,如sort、find、copy、for_each等
迭代器:扮演了容器与算法之间的胶合剂。
仿函数:行为类似函数,可作为算法的某种策略。
适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
空间配置器:负责空间的配置与管理。
容器:置物之所也,常用数据结构:数组,链表,树,栈,队列,集合,映射表
序列式容器 :强调值的排序,序列式容器中的每个元素均有固定的位置。
**关联式容器**:二叉树结构,各元素之间没有严格的物理上的顺序关系
算法:质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等
迭代器:提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
vector 通过迭代器访问容器 vector 中的数据
vector<int>::iterator itBegin = v.begin();
起始迭代器,指向容器中第一个元素
vector<int>::iterator itBegin = v.end(); 结束迭代器,指向容器中最后一个元素的下一个位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 vector <int > v;v.push_back(10 ); v.push_back(11 ); v.push_back(12 ); vector <int >::iterator itBegin = v.begin ();vector <int >::iterator itEnd = v.end ();while (itBegin != itEnd){ cout << *itBegin++<<endl ; } for (vector <int >::iterator it = v.begin (); it != v.end ; it++){ cout <<*it<<endl ; } void myPrint (int val) { cout << val <<endl ; } for_each(v.begin (), v.end (), myPrint);
vector容器中存放自定义数据类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <vector> #include <string> class Person {public : Person(string name, int age) { mName = name; mAge = age; } public : string mName; int mAge; }; void test01 () { vector <Person> v; Person p1 ("aaa" , 10 ) ; Person p2 ("bbb" , 20 ) ; Person p3 ("ccc" , 30 ) ; Person p4 ("ddd" , 40 ) ; Person p5 ("eee" , 50 ) ; v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); for (vector <Person>::iterator it = v.begin (); it != v.end (); it++) { cout << "Name:" << (*it).mName << " Age:" << (*it).mAge << endl ; } } void test02 () { vector <Person*> v; Person p1 ("aaa" , 10 ) ; Person p2 ("bbb" , 20 ) ; Person p3 ("ccc" , 30 ) ; Person p4 ("ddd" , 40 ) ; Person p5 ("eee" , 50 ) ; v.push_back(&p1); v.push_back(&p2); v.push_back(&p3); v.push_back(&p4); v.push_back(&p5); for (vector <Person*>::iterator it = v.begin (); it != v.end (); it++) { Person * p = (*it); cout << "Name:" << p->mName << " Age:" << (*it)->mAge << endl ; } } int main () { test01(); test02(); system("pause" ); return 0 ; }
vector 容器嵌套容器 1 2 3 4 5 6 7 8 9 10 11 12 13 vector <vector <int >> v;vector <int > v1;vector <int > v2;vector <int > v3;vector <int > v4;for ( vector <vector <int >>::iterator it = v.begin (); it != v.end (); it++ ){ for (vector <int >::iterator vit = (*it).begin (); vit != (*it).end (); (*it)++ ){ cout << *vit <<endl ; } }
插入和删除 功能描述:
函数原型:
push_back(ele);
//尾部插入元素ele
pop_back();
//删除最后一个元素
insert(const_iterator pos, ele);
//迭代器指向位置pos插入元素ele
insert(const_iterator pos, int count,ele);
//迭代器指向位置pos插入count个元素ele
erase(const_iterator pos);
//删除迭代器指向的元素
erase(const_iterator start, const_iterator end);
//删除迭代器从start到end之间的元素
clear();
//删除容器中所有元素
swap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <vector> #include <algorithm> using namespace std ;void myPrint (int val) { cout << val <<endl ; } int main () { vector <int > v1 = {1 ,2 ,3 ,4 ,5 }; vector <int > v2 = {6 ,7 ,8 ,9 ,10 }; for_each(v1.begin (), v1.end (), myPrint ); for_each(v2.begin (), v2.end (), myPrint ); v1.swap(v2); for_each(v1.begin (), v1.end (), myPrint ); for_each(v2.begin (), v2.end (), myPrint ); return 0 ; }
==swap 可以用来进行一个收缩内存的效果:vector\(v).swap(v);==
利用 reserve 预留空间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <vector> void test01 () { vector <int > v; v.reserve(100000 ); int num = 0 ; int * p = NULL ; for (int i = 0 ; i < 100000 ; i++) { v.push_back(i); if (p != &v[0 ]) { p = &v[0 ]; num++; } } cout << "num:" << num << endl ; } int main () { test01(); system("pause" ); return 0 ; }
String String 的本质是一个类
string 与 char * 的区别:
char * 是一个指针
string 是一个类,类内部封装了 char * ,管理整个字符串,是一个 char * 型的容器
构造函数:
string();
//创建一个空的字符串 例如: string str;string(const char* s);
//使用字符串s初始化
string(const string& str);
//使用一个string对象初始化另一个string对象
string(int n, char c);
//使用n个字符c初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <string> void test01 () { string s1; cout << "str1 = " << s1 << endl ; const char * str = "hello world" ; string s2 (str) ; cout << "str2 = " << s2 << endl ; string s3 (s2) ; cout << "str3 = " << s3 << endl ; string s4 (10 , 'a' ) ; cout << "str3 = " << s3 << endl ; } int main () { test01(); system("pause" ); return 0 ; }
string赋值操作 功能描述:
赋值的函数原型:
string& operator=(const char* s);
//char*类型字符串 赋值给当前的字符串
string& operator=(const string &s);
//把字符串s赋给当前的字符串
string& operator=(char c);
//字符赋值给当前的字符串
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个字符c赋给当前字符串
string字符串拼接 功能描述:
函数原型:
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);
//把字符串s的前n个字符连接到当前字符串结尾
string& append(const string &s);
//同operator+=(const string& str)
string& append(const string &s, int pos, int n);
//字符串s中从pos开始的n个字符连接到字符串结尾
string查找和替换 功能描述:
查找:查找指定字符串是否存在
替换:在指定的位置替换字符串
函数原型:
int find(const string& str, int pos = 0) const;
//查找str第一次出现位置,从pos开始查找
int find(const char* s, int pos = 0) const;
//查找s第一次出现位置,从pos开始查找
int find(const char* s, int pos, int n) const;
//从pos位置查找s的前n个字符第一次位置
int find(const char c, int pos = 0) const;
//查找字符c第一次出现位置
int rfind(const string& str, int pos = npos) const;
//查找str最后一次位置,从pos开始查找
int rfind(const char* s, int pos = npos) const;
//查找s最后一次出现位置,从pos开始查找
int rfind(const char* s, int pos, int n) const;
//从pos查找s的前n个字符最后一次位置
int rfind(const char c, int pos = 0) const;
//查找字符c最后一次出现位置
string& replace(int pos, int n, const string& str);
//替换从pos开始n个字符为字符串str
string& replace(int pos, int n,const char* s);
//替换从pos开始的n个字符为字符串s
string字符串比较 功能描述:
比较方式:
= 返回 0
> 返回 1
< 返回 -1
函数原型:
int compare(const string &s) const;
//与字符串s比较
int compare(const char *s) const;
//与字符串s比较
string字符存取 string中单个字符存取方式有两种
char& operator[](int n);
//通过[]方式取字符
char& at(int n);
//通过at方法获取字符
string插入和删除 功能描述:
函数原型:
string& insert(int pos, const char* s);
//插入字符串
string& insert(int pos, const string& str);
//插入字符串
string& insert(int pos, int n, char c);
//在指定位置插入n个字符c
string& erase(int pos, int n = npos);
//删除从Pos开始的n个字符
string子串 功能描述:
函数原型:
string substr(int pos = 0, int n = npos) const;
//返回由pos开始的n个字符组成的字符串
Deque容器 功能:双端数组,可以对头端进行插入删除操作
deque 与 vector 的区别:
vector 对于头部的插入删除效率低,数据量越大,效率越低
对于头部的插入删除速度会比 vector 快
vector 访问元素时的速度会比 deque 快
deque 内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真是数据,中控器维护的是每个缓冲区的地址,使得使用 deque 时像一片连续的内存空间
deque 构造函数 函数原型:
deque<T>
deqT; //默认构造形式
deque(beg, end);
//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);
//构造函数将n个elem拷贝给本身。
deque(const deque &deq);
//拷贝构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <deque> void printDeque (const deque <int >& d) { for (deque <int >::const_iterator it = d.begin (); it != d.end (); it++) { cout << *it << " " ; } cout << endl ; } void test01 () { deque <int > d1; for (int i = 0 ; i < 10 ; i++) { d1.push_back(i); } printDeque(d1); deque <int > d2 (d1.begin (),d1.end ()) ; printDeque(d2); deque <int >d3(10 ,100 ); printDeque(d3); deque <int >d4 = d3; printDeque(d4); } int main () { test01(); system("pause" ); return 0 ; }
deque 大小操作 deque 无容量限制,不像 vector 有限制,所以没有 .capacity 接口
函数原型:
deque.empty();
//判断容器是否为空
deque.size();
//返回容器中元素的个数
deque.resize(num);
//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <deque> void printDeque (const deque <int >& d) { for (deque <int >::const_iterator it = d.begin (); it != d.end (); it++) { cout << *it << " " ; } cout << endl ; } void test01 () { deque <int > d1; for (int i = 0 ; i < 10 ; i++) { d1.push_back(i); } printDeque(d1); if (d1.empty()) { cout << "d1为空!" << endl ; } else { cout << "d1不为空!" << endl ; cout << "d1的大小为:" << d1.size () << endl ; } d1.resize(15 , 1 ); printDeque(d1); d1.resize(5 ); printDeque(d1); } int main () { test01(); system("pause" ); return 0 ; }
deque 插入和删除 函数原型:
两端插入操作:
push_back(elem);
//在容器尾部添加一个数据
push_front(elem);
//在容器头部插入一个数据
pop_back();
//删除容器最后一个数据
pop_front();
//删除容器第一个数据
指定位置操作:
insert(pos,elem);
//在pos位置插入一个elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);
//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);
//在pos位置插入[beg,end)区间的数据,无返回值。
clear();
//清空容器的所有数据
erase(beg,end);
//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);
//删除pos位置的数据,返回下一个数据的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #include <deque> void printDeque (const deque <int >& d) { for (deque <int >::const_iterator it = d.begin (); it != d.end (); it++) { cout << *it << " " ; } cout << endl ; } void test01 () { deque <int > d; d.push_back(10 ); d.push_back(20 ); d.push_front(100 ); d.push_front(200 ); printDeque(d); d.pop_back(); d.pop_front(); printDeque(d); } void test02 () { deque <int > d; d.push_back(10 ); d.push_back(20 ); d.push_front(100 ); d.push_front(200 ); printDeque(d); d.insert(d.begin (), 1000 ); printDeque(d); d.insert(d.begin (), 2 ,10000 ); printDeque(d); deque <int >d2; d2.push_back(1 ); d2.push_back(2 ); d2.push_back(3 ); d.insert(d.begin (), d2.begin (), d2.end ()); printDeque(d); } void test03 () { deque <int > d; d.push_back(10 ); d.push_back(20 ); d.push_front(100 ); d.push_front(200 ); printDeque(d); d.erase(d.begin ()); printDeque(d); d.erase(d.begin (), d.end ()); d.clear (); printDeque(d); } int main () { test03(); system("pause" ); return 0 ; }
总结:
尾插 push_back( )
尾删 pop_back( )
头插 push_front( )
头删 pop_front( )
deque 容器数据存取 函数原型:
at(int idx);
//返回索引idx所指的数据
operator[];
//返回索引idx所指的数据
front();
//返回容器中第一个数据元素
back();
//返回容器中最后一个数据元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <deque> void printDeque (const deque <int >& d) { for (deque <int >::const_iterator it = d.begin (); it != d.end (); it++) { cout << *it << " " ; } cout << endl ; } void test01 () { deque <int > d; d.push_back(10 ); d.push_back(20 ); d.push_front(100 ); d.push_front(200 ); for (int i = 0 ; i < d.size (); i++) { cout << d[i] << " " ; } cout << endl ; for (int i = 0 ; i < d.size (); i++) { cout << d.at(i) << " " ; } cout << endl ; cout << "front:" << d.front() << endl ; cout << "back:" << d.back() << endl ; } int main () { test01(); system("pause" ); return 0 ; }
deque 排序 算法
利用 sort 算法进行对 deque 的排序
sort 算法也可以对 vector 等进行排序
sort(iterator begin, iterator end)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <deque> #include <algorithm> using namespace std ;void printDeque (const deque <int > &d) { for (deque <int >::const_iterator it = d.begin (); it != d.end (); it++ ){ cout << *it <<" " ; } cout <<endl ; } void test01 () { deque <int > d; d.push_back(30 ); d.push_back(40 ); d.push_back(100 ); d.push_front(200 ); d.push_front(300 ); printDeque(d); sort(d.begin (), d.end ()); cout <<"after sort" <<endl ; printDeque(d); } int main () { test01(); return 0 ; }
rand 随机数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <ctime> using namespace std ;int main () { unsigned int seed; seed = time(0 ); srand(seed); int first = rand() % 41 + 10 ; cout << first <<endl ; int second = rand() % 41 + 10 ; cout << second <<endl ; return 0 ; }
stack 构造函数:
stack<T> stk;
//stack采用模板类实现, stack对象的默认构造形式
stack(const stack &stk);
//拷贝构造函数
赋值操作:
stack& operator=(const stack &stk);
//重载等号操作符
数据存取:
push(elem);
//向栈顶添加元素
pop();
//从栈顶移除第一个元素
top();
//返回栈顶元素
大小操作:
empty();
//判断堆栈是否为空
size();
//返回栈的大小
queue 构造函数:
queue<T> que;
//queue采用模板类实现,queue对象的默认构造形式
queue(const queue &que);
//拷贝构造函数
赋值操作:
queue& operator=(const queue &que);
//重载等号操作符
数据存取:
push(elem);
//往队尾添加元素
pop();
//从队头移除第一个元素
back();
//返回最后一个元素
front();
//返回第一个元素
大小操作:
empty();
//判断堆栈是否为空
size();
//返回栈的大小
list 功能描述:
函数原型:
list<T> lst;
//list采用采用模板类实现,对象的默认构造形式:
list(beg,end);
//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);
//构造函数将n个elem拷贝给本身。
list(const list &lst);
//拷贝构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <list> void printList (const list <int >& L) { for (list <int >::const_iterator it = L.begin (); it != L.end (); it++) { cout << *it << " " ; } cout << endl ; } void test01 () { list <int >L1; L1.push_back(10 ); L1.push_back(20 ); L1.push_back(30 ); L1.push_back(40 ); printList(L1); list <int >L2(L1.begin (),L1.end ()); printList(L2); list <int >L3(L2); printList(L3); list <int >L4(10 , 1000 ); printList(L4); } int main () { test01(); system("pause" ); return 0 ; }
list 成员函数
list 数据存取 功能描述:
函数原型:
front();
//返回第一个元素。
back();
//返回最后一个元素。
list 反转和排序 功能描述:
函数原型:
reverse();
//反转链表
sort();
//链表排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 void printList (const list <int >& L) { for (list <int >::const_iterator it = L.begin (); it != L.end (); it++) { cout << *it << " " ; } cout << endl ; } bool myCompare (int val1 , int val2) { return val1 > val2; } void test01 () { list <int > L; L.push_back(90 ); L.push_back(30 ); L.push_back(20 ); L.push_back(70 ); printList(L); L.reverse(); printList(L); L.sort(); printList(L); L.sort(myCompare); printList(L); } int main () { test01(); system("pause" ); return 0 ; }
list 排序案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <list> #include <string> class Person {public : Person(string name, int age , int height ) { m_Name = name; m_Age = age; m_Height = height ; } public : string m_Name; int m_Age; int m_Height; }; bool ComparePerson (Person& p1, Person& p2) { if (p1.m_Age == p2.m_Age) { return p1.m_Height > p2.m_Height; } else { return p1.m_Age < p2.m_Age; } } void test01 () { list <Person> L; Person p1 ("刘备" , 35 , 175 ) ; Person p2 ("曹操" , 45 , 180 ) ; Person p3 ("孙权" , 40 , 170 ) ; Person p4 ("赵云" , 25 , 190 ) ; Person p5 ("张飞" , 35 , 160 ) ; Person p6 ("关羽" , 35 , 200 ) ; L.push_back(p1); L.push_back(p2); L.push_back(p3); L.push_back(p4); L.push_back(p5); L.push_back(p6); for (list <Person>::iterator it = L.begin (); it != L.end (); it++) { cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << " 身高: " << it->m_Height << endl ; } cout << "---------------------------------" << endl ; L.sort(ComparePerson); for (list <Person>::iterator it = L.begin (); it != L.end (); it++) { cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << " 身高: " << it->m_Height << endl ; } } int main () { test01(); system("pause" ); return 0 ; }
关联式容器 使用关联式容器存储的元素,都是一个个的“键值对”( ) 。
C++ STL提供了4种关联式容器,map, set, multimap, multiset
关联式容器名称
特点
map
定义在 \ 头文件中,使用该容器存储的数据,其各个元素的键值必须是唯一的(不能重复),该容器会根据键值的大小进行自动升序排序
set
定义在 \ 头文件中,使用该容器存储的数据,各个元素键值完全相同,且各个元素的值不能重复(保证各元素键的唯一性),该容器会根据键值的大小进行自动升序排序。
multimap
与 map 区别在于,键值可以重复
multiset
存储的元素的值可以重复(一旦重复,意味着键也是重复的)
pair 类模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <utility> // pair #include <string> // string using namespace std ;int main () { pair <string , double > pair1; pair <string , string > pair2("STL教程" ,"http://stl/" ); pair <string , string > pair3(pair2); pair <string , string > pair4(make_pair("C++教程" , "http://cplus/" )); pair <string , string > pair5(string ("Python教程" ), string ("http://python/" )); cout << "pair1: " << pair1.first << " " << pair1.second << endl ; cout << "pair2: " << pair2.first << " " << pair2.second << endl ; cout << "pair3: " << pair3.first << " " << pair3.second << endl ; cout << "pair4: " << pair4.first << " " << pair4.second << endl ; cout << "pair5: " << pair5.first << " " << pair5.second << endl ; return 0 ; }
1 2 3 4 5 pair1: 0 pair2: STL教程 http://stl/ pair3: STL教程 http://stl/ pair4: C++教程 http://cplus/ pair5: Python教程 http://python/
在 pair4 中,make_pair() 是 \ 头文件中提供的,主要功能是生成一个 pair 对象,使得 make_pair 函数所创造的返回值作为一个临时对象传递给 pair() 的第4种构造函数—> 移动构造函数(不是拷贝构造函数)。
pair 的赋值操作
1 2 pair1.first = "C++" ; pair1.second = "https://cplus.com" ;
1 pair<string , string > pair = make_pair("c++" ,"https://cplus.com" );
\ 还提供了对于 pair 对象重载了 <, <=, >, >==, ==, != 运算符,可以进行比较大小之类的操作。
1 2 3 4 5 6 7 pair<string, int> pair1("C++", 100); pair<string, int> pair2("C++", 100); if (pair1 == pair2){ std ::cout << pair1 == pair2 <<std ::endl ; }else { std ::cout << pair1 != pair2 <<std ::endl ; }
无序关联式容器 unordered_map 无序关联式容器与关联式容器最主要的区别在于,无序关联式容器不会对存储的元素做默认的升序排序操作。
无序关联式容器擅长通过指定键来查找对应的值,而遍历容器中存储元素的效率不如关联式容器。
关联式容器的底层实现采用的树存储结构——红黑树结构。
无序容器的底层采用的是哈希表的存储结构。
无序容器
功能
unordered_map
存储键值对 类型的元素,其中各个键值对键的值不允许重复,且该容器中存储的键值对是无序的。
unordered_multimap
和 unordered_map 唯一的区别在于,该容器允许存储多个键相同的键值对。
unordered_set
不再以键值对的形式存储数据,而是直接存储数据元素本身(当然也可以理解为,该容器存储的全部都是键 key 和值 value 相等的键值对,正因为它们相等,因此只存储 value 即可)。另外,该容器存储的元素不能重复,且容器内部存储的元素也是无序的。
unordered_multiset
和 unordered_set 唯一的区别在于,该容器允许存储值相同的元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <string> #include <unordered_map> using namespace std ;int main () { std ::unordered_map <std ::string , std ::string > myMap; myMap["C++" ] = "cpp" ; myMap["Py" ] = "python" ; myMap["Java" ] = "java" ; string str = myMap.at("Java" ); cout << "str = " << str << endl ; for (auto iter = myMap.begin (); iter != myMap.end (); ++iter) { cout << iter->first << " " << iter->second << endl ; } return 0 ; }
1 2 3 4 5 6 7 8 unorderedMaptest.cpp:19:10: warning: 'auto' type specifier is a C++11 extension [-Wc++11-extensions] for (auto iter = myMap.begin(); iter != myMap.end(); ++iter) ^ 1 warning generated. str = java Java java Py python C++ cpp
向 unordered_map 中插入元素的方法
emplace 方法效率比 insert 高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <string> #include <unordered_map> using namespace std ;int main () { unordered_map <string , string > umap; pair<unordered_map <string , string >::iterator, bool > ret; ret = umap.emplace("STL教程" , "http://c.biancheng.net/stl/" ); cout << "bool =" << ret.second << endl ; cout << "iter ->" << ret.first->first << " " << ret.first->second << endl ; return 0 ; }
tuple tuple<>
为pair模板的泛化。
Morden C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 return vector <int >(begin (ans),end (ans));return {begin (ans), end (ans)};std ::unique_ptr <Foo> p = std ::make_unique<Foo>(...);auto p = std ::make_unique<Foo>(...);for (auto it = s.begin (); it != s.end (); ++it) ...auto it = s.find (x);map <string , int > m;for (auto && kv : m){ cout << kv.first<<" " << kv.secnod << endl ; } for (auto &&[key, val] : m){ cout <<key<<" " << val <<endl ; } int a = 3 , b =4 ;auto [x, y] = std ::tie(a, b); int &x = a;int &y = b;
lambda 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 bool comp (const pair<int , int >&a, const pair<int ,int >&b) { return a.second < b.second; } int main () { vector <pair<int ,int >> v = ...; std ::sort(begin (v), end (v), comp); } std ::sort(begin (v), end (v), [](const auto & a, const auto & b){ return a.second < b.second; }); int a = 1 ;auto foo = [a]( a += 1 );foo(); ASSERT(a==1 ); int a = 1 ;auto foo = [&a]( a += 1 );foo(); ASSERT(a==2 ); int a, b, c;auto foo = [=]{return a + b + c; } auto foo = [=, &a]{ a += b + c; }
new 内存分配 1 2 3 4 5 6 7 8 9 10 11 void *__CRTDECL operator new (size_t size ) _THROW1 (_STD bad_alloc) { void *p; while ((p = malloc (size )) == 0 ) if (_callnewh(size ) == 0 ) { _THROW_NCEE(_XSTD bad_alloc, ); } return (p); }
new 调用C语言标准库的 malloc 进行内存分配,并且在内存分配失败的时候会调用 _callnewh 来调用我们自定义的 new-handler,如果 new-handler 返回非零,new 运算符就会再次尝试内存分配,直到分配内存或 new-handler 返回了零,那么 new 此时将会以抛异常的形式来表示内存分配失败。
0拷贝 概念:一种避免CPU将数据从一块存储拷贝到另一块存储的技术,是指将数据直接从磁盘文件复制到网卡设备中,不需要经由程序操作。
好处:避免操作系统内核缓冲区之间进行数据拷贝操作;避免操作系统内核和用户应用程序地址空间两者之间进行数据拷贝操作。
实现及原理:
mmap() + write().
系统调用函数把内核缓冲区数据映射到用户空间,DMA将磁盘数据拷贝到内核缓冲区,应用进程跟随操作系统内核共享这个缓冲区。应用进程再次调用 write() ,操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中,一切过程都发生在内核态。
sendfile()
Linux 内核2.1中,提供专门发送文件的系统调用 sendfile() 。
由sendfile() 发起系统调用,操作系统由用户态空间切换到内核态空间
通过DMA引擎将数据从磁盘拷贝到内核态空间的输入socket缓冲区
将数据从内核空间拷贝到与之关联的socket缓冲区
将socket缓冲区的数据拷贝到协议引擎中
sendfile()系统调用结束,操作系统由用户态切换到内核态空间。
inline define 直接调用的区别
define 宏定义是单纯的字符替换,inline为代码嵌入(无函数调用跳转,无栈帧小号)
处理阶段,define在预处理阶完成字符替换,inline 在编译阶段完成
类型安全检查,define 无安全类型检查,inline 是一个函数,会进行类型安全检查
进程 线程 协程 干货 | 进程、线程、协程 10 张图讲明白了! - 知乎 (zhihu.com)
地址空间分布
操作系统采用虚拟内存技术,把进程虚拟地址空间划分为用户空间和内核空间。
用户空间
Linux 可用 size 命令查看编译后程序的各个内存区域大小
1 2 3 (base) root@cy:~/rust_Pro# size hello_world/target/release/hello_world text data bss dec hex filename 341457 13448 536 355441 56 c71 hello_world/target/release/hello_world
内核空间
x86 32位系统,Linux 内核地址空间指虚拟地址从 0xC0000000开始到 0xFFFFFFFF位置的高端内存地址空间,总容量1G的容量。包含,内核镜像、物理页面表、驱动程序等运行在内核空间。
进程
进程是程序执行的过程 ,包括了动态创建、调度和消亡的整个过程,进程是程序资源管理的最小单位 。
线程
线程是操作系统能够进行运算调度的最小单位。线程被包含在进程之中,是进程中实际运作单位,一个进程可以包含多个线程,线程是资源调度的最小单位 。
线程资源和开销
同一进程中多条线程共享全部该进程中的系统资源,如虚拟地址空间,文件描述符文件描述符和信号处理等。但同一进程中多个线程有各自的调用栈、寄存器环境、线程本地存储等。
线程创建的开销主要是线程堆栈的建立,分配内存的开销,开销不大,其中最大的开销发生在线程上下文切换的时候。
协程:轻量化的微线程。
协程的调度完全由用户控制,协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,再切回来时,回复先前保存的寄存器上下文和栈,直接将操作用户空间栈,完全没有内核切换的开销。
动态协程:协程的栈大小2KB,最大到1GB,可动态伸缩。而线程存储上下文寄存器和栈固定为2MB,不灵活且相对较大。