第一部分、数据类型

字符型

  • C和C++中的字符型变量值占用一个字节
  • 字符型变量并不是把字符本身存放到内存中存储,而是将对应的ASCII编码放入存储单元

第二部分、运算符

算术运算符

// cout << "10/0=" << 10 / 0 << endl; // 报错 除数不能为 0 。
cout << "0.9 / 0.2 = " << 0.9 / 0.2 << endl; // 4.5
cout << "0.1 % 0.4 = " << 0.1 % 0.4 << endl; // 小数不能进行取模运算
// cout << "10%0 = " << 10 % 0 << endl; // 取模就是相除后取余数。既然相除都会报错,取模就更不用说了。
cout << "10%230 = " << 10 % 20 << endl; // 10

第三部分 指针

3.1 指针的定义和使用和所占的内存空间


int a = 10;

// 1、指针定义的语法: 数据类型 * 指针变量名
int* p;
// 让指针记录变量a的地址
p = &a;
cout << "a的地址为" << &a <<  endl;
cout << "指针p的地址为" << p << endl;

// 2、解引用
// 可以通过解引用的方法来找到指针指向的内存
// 指针前加 * 代表解引用,找到指针指向的内存中的数据
cout << " *p = " << *p << endl;
*p = 1000;
cout << "after define a = " << a << endl;
cout << " *p = " << *p << endl;

// 3、 指针所占的内存空间
// 一个指针变量(不论什么类型)占4个字节、64位系统下占8个字节
cout << "sizeof (int *)= " << sizeof(int *) << endl;
cout << "sizeof (float *)= " << sizeof(float *) << endl;
cout << "sizeof (float *)= " << sizeof(double *) << endl;

3.2 空指针和野指针

// 空指针用于给指针变量进行初始化
int* p = NULL;

// 空指针是不可以进行访问的
*p = 1000;  // 报错

// 指针变量p指向内存地址编号为 0x1100的空间
int* p = (int*)0x1100;
// 访问野指针报错 (引发了异常: 读取访问权限冲突。)
cout << *p << endl;

空指针和野指针都不是我们申请的空间,不能随意访问

3.3 const修饰指针

// 1、const 修饰指针 --- 常量指针
int a = 10;
int b = 20;
const int* p1 = &a;

// 指针的指向可以改,指针指向的值不可以改
p1 = &b;
*p1 = 100; //error

// 2、const 修饰常量 --- 指针常量
int* const p2 = &a;
// 指针的指向不可以改,指针指向的值可以改

p2 = &b;  //error
*p2 = 200;

const 修饰指针,则指针不可以改,修饰变量,则变量不可以改

第四部分、结构体

4.1结构体数组

struct Student {
	string name;
	int age;
	int score;
};
struct Student studentList[3] = {
	{"郑家崇",19,132},
	{"郑家崇1",12,142},
	{"郑家崇2",21,92}
};
cout << studentList[1].score << endl;

4.2结构体指针

struct Student {
	string name;
	int age;
	int score;
};
struct Student stu = { "郑家崇",19,132 };

struct Student* p = &stu; 
// 声明一个结构体指针,指向某个结构体。
// 指针通过 -> 可以访问成语
cout << p->score << endl;

4.3 结构体中const使用场景(限定结构体中的成员不可变更)

struct Student {
	string name;
	int age;
	int score;
};

// 结构体中const使用场景(限定结构体中的成员不可变更)
void changeName(const Student* s)
{
	s->name = "winston"; // 报错
	cout << s->name << endl;
};


int main(void) {
	struct Student stu = { "郑家崇",19,132 };
	struct Student* p = &stu; 
	changeName(&stu);
}

第五部分、程序的内存模型

C++程序在执行时,将内存大方向划分为4个区域:

  • 代码区:存放函数体的二进制代码.由操作系统进行管理的。
  • 全局区:存放全局变量和静态变量以及常量。
  • 栈区:由编译器自动分配释放,存放函数的参数值、局部变量等。
  • 堆区:由程序员分配放.若程序员不释放,程序结束时由操作系统回收。

内存四区的意义:

不同区域存放的数据,赋予不同的生命周期,给我们最大的灵活编程。

5.1 程序运行后

栈区: 由编译器自动分配释放,存放函数的参数值,局部变量等。 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。

int* fun() {
	int a = 10;
	return &a;
}
int main(void) {
	int* p = fun();
	cout << *p << endl;
	cout << *p << endl; // 函数执行完成之后会自动被回收,所以这里就变得无法访问了。
}

堆区:

由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

在C++中主要利用new在堆区开辟内存

int *p = new int(10); //new方法返回一个内存地址

第六部分、引用

基本语法

数据类型 &别名 = 原名

int a = 10;
int &b = a;
b = 20;
cout << a << endl; // 20

引用的注意事项

  • 必须要初始化

引用做为函数的参数

当我们试图去交换两个变量的时候,我们常用地址传递,当然我们现在可以使用引用传递。引用会对实参进行修改

void swap(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
};

int main(void) {
	int a = 10;
	int b = 30;
	swap(a, b);
	cout << "a=" << a << endl; // 30

	cout << "b=" << b << endl;
}

常量引用

void change(const int& a) {
	a = 12;
};

int main(void) {
	int a = 10;
	change(a);
	cout << "a=" << a << endl; 
	
}

防止在函数中操作影响实参

第七部分 函数高级

7.1 注意事项

  • 如果函数声明中有默认参数,则函数定义就不能重复定义默认参数了。

7.2 函数重载的的注意事项

7.2.1 引用作为重载的条件

void func(int& a) {
	cout << "func(int& a) 调用" << endl;
}
void func(const int& a) {
	cout << "func(const int& a) 调用" << endl;
}

int main(void) {
	int a = 10;
	func(a);
	func(10); // 整型常量
}

第八部分、类和对象

8.1 拷贝构造

class Person
{
public:
	Person() {
		cout << "person的无参构造函数调用" << endl;
	};
	Person(int a) {
		cout << "Person的有参构造函数调用" << endl;
	};
	// 拷贝构造函数
	Person(const Person &p) {
		age = p.age;
		cout << "Person的拷贝构造函数调用" << endl;
	};
	~Person() {
		cout << "deconstruct" << endl;
	}

private:
	int age;
};

// 构造函数的调用

int main(void) {
	  Person p0(); // 调用默认构造函数的时候,不需要加上()。否则编译会当成这是一个函数去调用,然而并不存在这个函数
	  Person p1;
	  Person p2(10);
	  Person p3(p2);

	  Person(10); //  匿名对象 特点:当前执行结束后,系统会立即回收掉匿名对象
	  Person(p3); //  不要利用拷贝构造函数,去初始化匿名对象。编译器会认为是 Person(p3) === Person p3;导致重复定义错误。

	
	  // 隐式转换法
	  Person p4 = 10; //   Person p4 = Person(10)
	  Person p5 = p4; //   Person p4 = Person(p4)
}

以上代码有以下几点需要注意:

  • 调用默认构造函数的时候,不需要加上()。否则编译会当成这是一个函数去调用,然而并不存在这个函数
  • 匿名对象 特点:当前执行结束后,系统会立即回收掉匿名对象
  • 不要利用拷贝构造函数,去初始化匿名对象。编译器会认为是 Person(p3) === Person p3; 导致重复定义错误

8.2 拷贝构造函数的调用时机

C++中拷贝构造函数调用时机通常有三种:

  • 使用一个已经创建完毕的对象去初始化一个新的对象
Person p2(10);
Person p3(p2);
  • 值传递的方式给函数参数传值
void doWork(Person p){

}
void test(){
	Person p;
	doWork(p)
}

8.3 构造函数的调用规则

默认情况下,C++编译器至少给一个类添加3个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性值进行拷贝

构造函数调用规则如下:

  • 如果存在自定义构造函数,C++不再提供默认无参构造,但会提供默认拷贝构造。
  • 如果存在自定义拷贝构造函数,C++不会再提供其他构造函数。
  • 如果存在定义有参构造函数,则C++将不再提供默认无参构造函数。(这意味这如果你不传参数去调用一个有参的构造函数,将会报错)

8.4 静态成员

静态成员就是在成员变量和成员函数前加上static关键字,称为静态成员

静态成员为:

  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段进行内存分配
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享一个静态成员函数
    • 静态成员函数只能访问静态成员变量

静态成员可以通过对象和类名进行访问

class Car {
public:
	static int engine;
	int static getEngine() {
		return engine;
	};
};

int Car::engine = 120;

int main(void) {
	Car car;
	Car car1;
	car1.engine = 300;
	cout << "car.engine" << car.engine << endl; // 300

	cout << "Car::engine" << Car::engine << endl; 
	cout << "car.getEngine()" << car.getEngine() << endl;
	cout << "car::getEngine()" << car::getEngine() << endl;
}

8.5 成员对象和函数的内存空间分配

class Person {
	
};
int main(void) {
	Person p;
	cout << "sizeof(p)" << sizeof(p) << endl; // 1
}

空对象占用一个字节的内存空间

class Person {
	int a; // 非静态成员变量,属于类的对象上
	static int b; // 静态成员变量,不属于类的对象上
	void func(){} // 非 静态成员函数,不属于类的对象上
};
int main(void) {
	Person p;
	cout << "sizeof(p)" << sizeof(p) << endl; // 4
}

8.6 this的用途

  • 当形参和成员变量同名时,可用this指针来区分
  • 返回对象本身
class Person {
public:
	Person(int age) {
		this->age = age;
	};

	Person& PersonAddPerson(Person p) { // 返回对象本身进行链式调用
		this->age = age + p.age;
		return *this;
	};
	int getAge() {
		return age;
	};
private:
	int age;
};

int main(void) {
	Person p1(10);
	Person p2(10);
	p2.PersonAddPerson(p1).PersonAddPerson(p1);
	cout << "p.age = " << p2.getAge() << endl;
}

8.7 const修饰成员函数

常函数:

  • 在成员函数后加const,这个函数称为常函数。
  • 常函数不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改。

常对象:

  • 声明对象前加const,这个对象成为常对象
  • 常对象只能调用常函数

class Person {
public:
	void showAge() const{ // 修饰的时this指向,让指针的指向也不可以修改。
		this->age = 10;
	};
	void func(){}
private:
	mutable int age;
};

int main(void) {
	const Person p;
	p.showAge();
	p.func(); // error 不兼容的限定符
};

8.8 友元

全局函数做友元

class Building {
public:
	friend void myFriendFunc(Building* building);
	Building() {
		sittingRoom = "客厅";
		bedRoom = "卧室";
	};
	string sittingRoom;
private:
	string bedRoom;
};

void myFriendFunc(Building* building) {
	cout << building->sittingRoom << endl;
	cout << building->bedRoom << endl;
};

int main(void) {
	Building building;
	myFriendFunc(&building);
}

8.9 运算符重载

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

8.9.1 加号运算符重载

class Person
{
public:
	// 成员函数重载
	Person operator+(Person &p) {
		Person temp;
		temp.a = this->a + p.a;
		temp.b = this->b + p.b;
		return temp;
	};
	int a;
	int b;
};

// 全局函数重载
Person operator+(Person& p1,Person& p2) {
	Person temp;
	temp.a = p1.a + p2.a;
	temp.b =p1.b + p2.b;
	return temp;
};

int main(void) {
	Person p1;
	p1.a = 10;
	p1.b = 10;
	Person p2;
	p2.a = 10;
	p2.b = 10;

	Person p3 = p1 + p2;
	cout << "p3.a = " << p3.a << "; p3.b = " << p3.b << endl;
}

8.9.2 左移运算符重载

 
class Person
{
public:
	int a;
	int b;
};

ostream & operator<<(ostream &cout,Person &p) {
	cout << "p.a = " << p.a << "; p.b = " << p.b;
	return cout; // 返回标准输出流,方便进行链式调用
}

int main(void) {
	Person p1;
	p1.a = 10;
	p1.b = 10;
	cout << p1 << endl;
}

8.9.3 赋值运算符重载

默认情况下,C++编译器至少给一个类添加4个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性值进行拷贝
  4. 赋值运算符operator=,对属性值进行浅拷贝
class Person {
public:

	Person(int age) {
		this->age = new int(age);
	};

	/*
	Person& operator=(Person &p){
		if (age != NULL) {
			delete age;
			age = NULL;
		};
	 age = new int(*p.age);
	 return *this;
	};
	*/

	~Person() {
		if (age != NULL) {
			delete age;
			age = NULL;
		};
		cout << "destructor" << endl;
	};

	int *age;

};

int main(void) {
	Person p1(10);
	cout << "p1的年龄为:" << *p1.age << endl;
	Person p2(20);
	p2 = p1;
}

默认赋值运算符,只是浅拷贝,对于存在堆区的数据只是指针拷贝。 然后在析构过程中,*age会在p1中被回收,后面p2在访问就报错了。

这就需要我们重载赋值运算符,重新开辟一块堆区的数据给新的对象。

8.9.4 关系运算符重载

class Person {
public:
	int a;
	int b;
	Person(int xa, int xb):a(xa),b(xb) {

	}
	bool operator==(Person& p) {
		if (this->a == p.a && this->b == p.b) {
			return true;
		};
		return false;
	}
};
int main(void) {
	Person p1(10,12);
	Person p2(10, 132);
	if (p1 == p2) {
		cout << "p1 == p2" << endl;
	}
	else {
		cout << "p1 != p2" << endl;
	}
}

8.10 继承

8.10.1 继承方式

继承的语法: class 子类 : 继承方式 父类

继承的方式一共有三种:

  • 公共继承
  • 保护继承
  • 私有继承

其继承规则如下图: warn

8.10.2 继承同名成员函数的处理方式

当子类和父类中出现了同名的成员,如何分别范围呢?

  1. 子类可以直接访问到子类中的同名成员
  2. 子类对象加作用域可以访问到父类同名成员
class Base {
public:
	void func() {
		cout << "Base调用 " << endl;
	};
};

class Son :public Base {
public:
	void func() {
		cout << "Son调用 " << endl;
	}
};

int main(void) {
	Son son;
	son.func();
	son.Base::func();
}

8.10.3 菱形继承问题

菱形继承就是说一个类继承两个父类,且两个父类又继承自同一基类。

菱形继承存在以下问题:

  1. 若在子类中访问祖父基类中的成员时,编译器不知道是哪个父基类的成员。
  2. 子类中会有两份个父基类的相同的数据,而子类往往只需要一份,这个问题如何解决?

这个时候我们就可以使用虚继承

虚继承其实就是继承基类的指针(vbptr - 虚基类指针)。

warn

这是编译器不知道你访问的是哪个父级的age

class Animal {
public:
	int age;
};

class Sheep :virtual public Animal {};

class Camel :virtual public Animal {};

class SheepCamel :public Sheep, public Camel {};

int main(void) {
	SheepCamel sc;
	sc.Sheep::age = 20;
	sc.Camel::age = 30;
	sc.age = 100;
}

使用虚函数就完美解决这个问题了

8.11 多态

多态是指调用同一个函数名,根据需要实现不同的功能。

  • 编译时的多态性:静态多态(函数重载、运算符重载)
  • 运行时的多态性:动态多态(虚函数)

函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时就能决定调用的是哪个函数。静态多态性又称编译时的多态性。静态多态是通过函数重载、运算符重载实现的。

运行时的多态性是指在程序执行之前,根据函数名和参数无法确定应该调用哪一个函数,必须在程序的执行过程中,根据具体的执行情况来动态地确定。动态多态是通过虚函数(virtual function)实现的。

8.11.1 多态的基本语法

class Animal {
public:
	virtual void speak() {
		cout << "animal speak" << endl;
	}
};
class Cat :public Animal {
public:
	void speak() {
		cout << "Cat speak" << endl;
	}
};
void doSpeak(Animal& animal) {
	animal.speak();
};
int main(void) {
	Cat cat;
	doSpeak(cat);
}

动态多态的满足条件

  1. 有继承关系
  2. 子类重写父类的虚函数

动态多态的使用

  1. 使用父类的指针或引用 指向子类对象

注意点

  1. 子类是重写父类的虚函数(注意不是重载,即所有参数和返回值都保持和父类一致)

8.11.2 纯虚函数和多态类

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

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

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

当类中有了纯虚函数,这个类就是抽象类

抽象类的特点:

  • 无法实例化对象
  • 子类必须重写父类的纯虚函数,否则也属于纯虚函数。
#include <iostream>
using namespace std;

class Base{
public:
    void show(int a)=0;
    
};
int main() {
    Base b; // 无法实例化对象
    return 0;
}

8.11.3 虚析构和纯虚析构

动态多态使用时,如果在子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类中的析构代码

class Base{
public:
   Base(){
	 cout << "父类的构造函数调用" << endl;
   };
   ~Base(){
	 cout << "父类的析构函数调用" << endl;
   };
   void speak() = 0 ;
};

class Son : public Base{
public:
   Son(string name){
	this->name = new String(name);
	cout << "子类的构造函数调用" << endl;
   };
   ~Son(){ // 不会被调用,子类堆区中的变量将无法被回收
	if(name!=NULL){
		delte name;
		name = null;
	}
	 cout << "子类的析构函数调用" << endl;
   };
   void speak(){
     cout << *name << "在说话" << endl;
   };
   int *name;
}
int main(void){
	Base *base = new Son();
	base->speak();
}

解决方式:将父类中的析构函数改为虚析构或纯虚析构

 virtual ~Base(){
	 cout << "父类的析构函数调用" << endl;
 };

虚析构和纯虚析构的共性:

  • 可以使父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构的区别:

  • 如果是纯虚析构,则该类也属于抽象类,无法被实例化。

第九部分 文件操作

程序运行时产生的数据都属于临时数据,程序一旦结束就会被释放

通过文件可以将数据持久化

文件类型分为两种:

  1. 文本文件 - 文件以ASCII的形式存储在计算机中。
  2. 二进制文件 - 文件以二进制的形式存储在计算机中。

操作文件的三大类:

  1. ofstram 写操作
  2. ifstram 读操作
  3. fstream 读写操作

9.1 文本文件

9.1.1 写文件

ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "my name is winston" << endl;
ofs.close();

9.1.2 读文件

ifstream ifs;
ifs.open("test.txt", ios::in);
if (ifs.is_open()) {
	// 读数据
	// 第一种方式
	char buf[1024] = { 0 };
	while (ifs >> buf)
	{
		cout << buf << endl;
	};

	// 第二种方式
	char buf[1024] = { 0 };
	while (ifs.getline(buf, sizeof(buf)))
	{
		cout << buf << endl;
	}
	*
	// 第三种方式
	string buffer;
	while (getline(ifs,buffer)) {
		cout << buffer << endl;
	}
}
else {
	cout << "文件路径不存在" << endl;
}

第十部分、模板

10.1 函数模板

template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用类型数据
void mySwap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
};
int main(void) {
	int x = 10;
	int y = 20;
	// 两种方式使用函数模板
	// 1.自动类型推倒
	mySwap(x, y);

	double a = 1.2;
	double b = 2.3;
	// 2.显示指定类型
	mySwap<double>(a, b);
}

10.1.1 普通函数和函数模板的调用规则

普通函数和函数模板的调用规则:

  • 如果函数模板和普通函数都可以被调用,优先调用普通函数。
void Myprint(int a) {
	cout << "普通函数调用" << a << endl;
};
template <typename T>
void Myprint(T a) {
	cout << "函数模板调用" << a << endl;
};

int main(void) {
	Myprint(10); // 普通函数调用
}
  • 可以通过空函数模板参数列表,强制调用函数模板。

void Myprint() {
	cout << "普通函数调用"  << endl;
};
template <typename T>
void Myprint() {
	cout << "函数模板调用"  << endl;
};

int main(void) {
	Myprint<int>();  // 函数模板调用
}
  • 函数模板可以发生重载
  • 如果函数模板可以发生更好的匹配,优先调用函数模板。

10.1.2 模板的局限性

局限性:如果用户传入参数类型时自定义的数据类型,那么模板将无法处理。

template <typename T>
bool compare(T a, T b) {
	if (a > b) {
		return true;
	};
	return false;
};
class Person {
	int a=10; 
	int b=20;
};
int main(void) {
	compare(10, 12);
	Person p1;
	Person p2;
	compare(p1, p2); // 报错,无法处理
}

为了解决这个问题,我们可以提供模板的重载,可以为这些特定的类型提供具体化的模板


class Person {
public:
	Person(int a, int b) {
		this->a = a;
		this->b = b;
	};
	int a;
	int b;
};

template <class T>
bool mycompare(T &a, T &b) {
	if (a > b) {
		return true;
	};
	return false;
};



// 具体化模板,显示具体化的原型,以template<>开头,并通过名称来指出类型
// 具体化优先于常规模板

template<> bool mycompare(Person& p1, Person& p2) {
	if (p1.a == p2.a && p1.b == p2.b) {
		return true;
	};
	return false;
};

int main(void) {
	Person p1(1,12);
	Person p2(1,12);
	mycompare(p1, p2);
}

10.2 类模板

10.2.1 类模板和函数模板的区别

类模板和函数模板的区别

  1. 类模板没有自动类型推导。
  2. 类模板在参数列表中可以有默认参数类型。
template<class T,class U=int> // 默认参数类型
class Person {
public:
	Person(T name,U age) {
		this->name = name;
		this ->age = age;
	};
	void show() {
		cout << "name = " << name << " ; age = " << age << endl;
	};
	T name;
	U age;
};

int main(void) {
	Person <string>p("winston", 18); // 没有自动类型推导,必须显示指定类型
	p.show();
};

10.2.2 类模板中的成员函数的创建时机

类模板中的成员函数的创建时机

  • 类模板中的成员函数在创建时才会去创建

10.2.3 类模板与继承

当类模板碰到继承的时候,需要注意以下几个问题:

  • 当子类继承的父类是一个类模板的时候,子类在声明的时候,要指定父类中泛型的具体类型。
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定父类中的泛型类型,子类也需变成类模板
template <class T>
class Base {
	T age;
};
class Son : public Base{}; // error 缺少类模板的参数列表
class Son : public Base<int> {}; // 正确

10.2.4 类模板成员函数的类外实现

template<class T, class U>
class Person {
public:
	Person(T name, U age);
	void show();
	T name;
	U age;
};

// 构造函数的类外实现
template<class T, class U >
Person<T,U>::Person(T name, U age) {
this->name = name;
this->age = age;
};

// 成员方法类外实现
template<class T, class U >
void Person<T, U>::show() {
	cout << "name = " << this->name << " ; age = " << this->age << endl;
};
int main(void) {
	Person <string,int>p("winston", 18);
	p.show();
};

10.2.4 类模板分文件编写

当把类模板的声明和实现分开编写的时候,会报一个链接失败的错误。

原因是:类模板中的成员函数在创建时才会去创建。所以你直接引入一个.h的头文件时找不到成员的, 就会报错。

// Person.h
#pragma once
#include <iostream>
#include <string>

template<class T, class U>
class Person {
public:
	Person(T name, U age);
	void show();
	T name;
	U age;
};

// Person.cpp
#include <iostream>
#include "Person.h";
using namespace std;
template<class T, class U >
Person<T, U>::Person(T name, U age) {
	this->name = name;
	this->age = age;
};
template<class T, class U >
void Person<T, U>::show() {
	cout << "name = " << this->name << " ; age = " << this->age << endl;
};

// main.cpp

#include "Person.h";
int main(void) {
	Person <string,int>p("winston", 18); // linker error
	p.show();
};

解决方法有两个:

  1. 直接包含.cpp源文件
  2. 将声明和实现放到同一个.hpp文件中。.hpp时约定俗成的后缀,并不强制。

10.2.5 类模板和友元

在类模板内部声明一个友元函数,可以将这个函数变成全局函数。

template <class T,class U>
class Person {
	friend void printPerson(Person <T, U> p) {
		cout << p.name << " is = " << p.age << endl;
	};
public:
	Person(T name,U age) {
		this->name = name;
		this->age = age;
	};
private:
	T name;
	U age;
};

void test() {
	Person<string,int> p("winston", 12);
	printPerson(p);
};
int main() {
	test();
}

第十一部分、STL初识

11.1 STL的诞生

  • 大多数情况下,数据结构和算法都未能有一套标准,导致被迫从事大量的重复工作。
  • 为了建立数据结构和算法的一套标准,诞生了STL。

11.2 STL的基本概念

  • STL (Standard Template Library) 标准模板库
  • STL广义上分为:容器(container),算法(algorithm),迭代器(iterator)
  • 容器算法之间通过迭代器进行无缝链接
  • STL几乎所有的代码都采用了模板类或函数模板。

11.3 STL中的容器、算法、迭代器

STL容器就是运用数据结构的实现

11.3.1 vector存放内置数据类型

容器:vector

算法:for_each

迭代器:vector::iterator

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void callback(int value) {
	cout << value << endl;
};

void test() {
	// 创建一个vector容器,
	vector<int> v;
	v.push_back(10);
	v.push_back(11);

	// 第一种遍历方式
	vector<int>::iterator itBegin = v.begin();
	vector<int>::iterator itEnd = v.end(); // 最后一个元素的后一位

	while (itBegin != itEnd) {
		cout << *itBegin << endl;
		itBegin++;
	}

	// 第二种遍历方式
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << endl;
	};

	// 第三种遍历方式 使用STL提供的遍历算法
	for_each(v.begin(), v.end(), callback);
};

11.3.2 vector存放自定义数据类型

class Person {
public:
	Person(int xa, int xb) :a(xa), b(xb) {};
	int a;
	int b;
};
void callback(Person p) {
	cout << p.a << endl;
	cout << p.b << endl;
};
void test2() {
	vector<Person> v;
	Person p1(10, 12);
	Person p2(110, 112);

	v.push_back(p1);
	v.push_back(p2);
	for_each(v.begin(), v.end(), callback);
};

第十二部分、STL常用容器

12.1 string容器

12.1.1 string的基本概念

本质:

  • string 是C++风格的字符串,而string本质上是一个类。

string 和char *区别

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

12.1.2 string的构造函数

构造函数原型

  • string(); 创建一个空的字符串 例如:string:str;
  • string(const char * s) 用字符串s区去初始化
  • string(const string & str) 拷贝构造
  • string(int m,char c) 使用n个字符c初始化
string str;
cout << str << endl;

const char* str1 = "hello world";
string str2(str1);
cout << "str2 = " << str2 << endl;

string str3(str2);
cout << "str3 = " << str3 << endl;

string str4(10, 'c');
cout << "str4 = " << str4 << endl;

12.1.3 string的基本操作

12.1.3.1 赋值操作

string str;
str = "str4"; // str4
str.assign("muname"); // muname
str.assign("muname",2); // mu
cout << str << endl;

12.1.3.2 拼接操作

string str;
str += "str"; // str
cout << str << endl;
str.append("append"); // strappend
cout << str << endl;
str.append("append",0,3); // strappendapp
cout << str << endl;

12.1.3.3 存取操作

string str="abcdefg";
cout << "str.size()" << str.size() << endl;
cout << "str[0]=" << str[0] << endl;
cout << "str.at(1)=" << str.at(1) << endl;

12.1.3.4 插入删除操作

string str="abcdefg";
str.insert(1, "www");
cout << str << endl; // awwwbcdefg
str.erase(0, 4);
cout << str << endl; // bcdefg

12.2 vector容器

12.2.1 vector容器的基本概念

功能

  1. vector数据结构和数组非常类似,也称为单端数组

vector与普通数组的区别

  1. 数组是静态空间,而vector可以动态扩展。

动态扩展

  • 并不是在原有空间之后续接新空间,而是寻找一块更大的空间,然后将原数据拷贝至 新的空间,释放旧的空间。

warn

12.2.2 vector容器的构造函数

  • vector v; 默认构造函数
  • verctor(v.begin(),b.end()) 将v.begin(),b.end()区间中的元素拷贝给本身
  • vector(n,ele) 用n个ele构造本身
  • vector(const vector &vec) 拷贝构造函数
void callback(int value) {
	cout << value << endl;
};

int main(void) {
	vector<int> v(10,100);
	for_each(v.begin(), v.end(), callback);
}

12.2.3 vector容量和大小

warn

vector<int> v(10, 100);
cout << "v.size()=" << v.size() << endl;
cout << "v.capacity()=" << v.capacity() << endl;
v.resize(15, 12);
cout << "after resize capacity=" << v.capacity() << endl;
cout << "after resize size =" << v.size() << endl;
for_each(v.begin(), v.end(), callback);

12.2.4 vector数据存取

warn

12.2.5 vector互换容器

当给一个vector进行resize操作时,其capacity并不会变化,这意味着vector所占的内存空间一直未改变。 这时候我们可以使用swap把更小的一块内存换给他,合理优化空间。

12.2.5 vector预留空间

当我们使用循环多次给vector增加数据的时候,由于vector一开始不知道需要开辟多少内存,所以当内存不够的时候, 又重新开辟空间而后把之前的值拷贝过去。预留空间就是告诉vector将有定量的数据插入。避免重新开辟内存导致的性能浪费。

vector<int> v1;
v1.reserve(10000);

12.3 deque容器

warn

12.4 stack容器

warn

12.4.1 stack容器的常用接口

warn

stack<int> s;
s.push(1);
s.push(2);
cout << "栈的大小" << s.size() << endl;
// 遍历一个栈,只要不为空的时候一直查看栈顶,并执行出栈操作。
while (!s.empty())
{
	cout << "栈顶元素为" << s.top() << endl;
	// 出栈
	s.pop();
};

12.5 queue容器

warn

场景联想:排队打饭。

只有队头和对尾允许访问