- 将编译器优化的部分做了讲解,理解了平时模糊的点;
一.基础概念
1.变量
- 给指定的内存空间取名字,方便操作这段内存;
2.常量
- 用于记录程序中不可以更改的数据;
- 分类:宏常量和const修饰的常量;
3.关键字
- 预先保留的单词或标识符;
4.标识符起名规则
- 给标识符起名有一套规则;
5.数据类型
- C++规定创建一个变量或常量,必须要指出相应的数据类型,否则无法给变量分配内存;
- 意义:给变量分配合适的内存空间;
6.sizeOf
- 统计数据类型所占用的大小
7.数据类型-实型
- 用于表示小数;
8.字符型
- 字符变量用于显示单个字符;
- 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元;
9.转义字符
- 用于不能显示出来的ASCII字符;
10.字符串
- 两种风格:
- C语言风格:char 变量名[] = “字符串值”
- C++风格
11.布尔类型
- 布尔数据类型代表真或假的值;
- 只要是非0的都代表着真;
12.数据的输入
- cin
13.算术运算符
- 加减乘除
- 加加与减减:前置,先对变量进行运算,后对表达式进行运算
14.赋值/比较/逻辑运算符
15.结构结构/循环结构/跳转语句
16.一维数组
- 数组中的每一元素都是相同的数据类型
- 数组是有连续的内存位置组成的
16.1.定义方式
16.2.数组名
- 用途:1.可以统计整个数组在内存中的长度;2.可以获取数组在内存中的首地址,(int)数组名;
16.3.冒泡排序
- 最常用的排序算法
- 比较相邻的元素,如果第一个比第二个大,就交换它们;
- 对每一对相邻元素做同样的工作,执行完毕,找到第一个最大值;
- 重复以上步骤,每次比较次数-1,直到不需要比较;
- 外层循环次数为:数组长度-1,内层循环次数为数组长度-1-当前轮数;
17.二维数组
17.1.定义方式
- 方式1:数据类型 数组名 [行 ][列 ]
- 方式2:数据类型 数组名 [行 ][列 ] = {{数据1,数据2},{数据3,数据4}}
- 方式3:数据类型 数组名 [行 ][列 ] =
- 方式4:数据类型 数组名 [ ][列 ] =
17.2.循环
- 外层是行,内层是列
17.3.二维数组名称用途
- 查看内存占用空间大小
- 查看二维数组的首地址
18.函数
- 将一段代码封装起来,减少重复代码
- 定义/调用/值传递(形参变化不会影响实参),括号内的叫做形参
- 函数的常见样式:无参无返/有参无返/无参有返/有参有返
- 函数的声明
- 作用:在函数定义之前告诉编译器,有这么一个函数存在
- 好处:提高代码可读性/支持分离编译/提前检查错误/支持函数重载/避免隐式函数声明;
- 声明可以多次,但调用只能一次
18.1.函数的分文件编写(实践)
- 四个步骤
- 1.定义头文件(.h结尾)
- 2.定义源文件(.cpp结尾)
- 3.在头文件中写函数声明
- 4.在源文件中写函数定义
19.指针
- 必须掌握的技术
- 通过指针间接访问内存
19.1.定义指针
19.2.操作指针
- 通过接引用的方式来找到指针指向的内存
19.3.指针所占内存空间
- 32位的操作系统,占用4个字节;64位8个字节;
19.4.空指针
- 指针指向内存中编号为0的空间
- 用途:初始化指针变量
- 注意:空指针指向的内存是可以访问的
19.5.野指针
- 指针指向非法的内存空间
- 空指针和野指针都不是我们申请的空间,因此不能访问;
19.6.const修饰指针
- 在指针前面加上const,常量指针,特点:指针的指向可以更改,指针指向的值不可以修改;
- 在指针后面加上const,指针常量:特点:指针的指向不可以更改,指向的值可以更改;
- 指针前后都加const
19.7.指针和数组的配合使用
- 目的:利用指针访问数组的元素
- 代码敲一遍(DONE)
20.结构体
- 属于用户自定义的类型(一些类型集合组成的一个类型),允许用户存储不同的数据类型
20.1.定义方式
- 三种
#include<iostream>
#include<string>
using namespace std;
struct Student {
string name;
int age;
int score;
} s3;
int main() {
struct Student s1;//这里的struct关键字可以省略
//给s1属性赋值,通过.访问结构体变量中的属性
s1.name = "张三";
s1.age = 18;
s1.score = 100;
cout << "姓名" << s1.name << "年龄" << s1.age << "分数" << s1.score << endl;
//2.在创建结构体变量的同时给变量的属性进行赋值
struct Student s2 = { "李四",19,80 };
cout << "姓名" << s2.name << "年龄" << s2.age << "分数" << s2.score << endl;
//3.在定义结构体的时候顺便定义一个结构体变量
s3.name = "王五";
s3.age = 21;
s3.score = 100;
cout << "姓名" << s3.name << "年龄" << s3.age << "分数" << s3.score << endl;
system("pause");
return 0;
}
- 注意:1.定义结构体时的关键字是struct,不可以省略;2.创建结构体变量时,关键字struct可以省略;3.结构体变量利用操作符"."访问成员;
20.2.结构体数组
- 语法:struct 结构体名 数组名[元素个数] = {{},{},…
20.3.结构体指针
- 使用指针访问结构体中的成员
#include<iostream>
#include<string>
using namespace std;
struct Student {
string name;
int age;
int score;
} s3;
int main() {
//1.创建学生结构体变量
struct Student s = { "张三",18,100 };
//2.通过指针指向结构体变量
struct Student* p = &s;
//3.通过指针访问结构体变量中的数据
cout << "姓名" << p->name << "年龄" << p->age << "分数" << p->score << endl;
system("pause");
return 0;
}
20.4.结构体嵌套结构体
- 在结构体中可以定义另一个结构体作为成员,用来解决实际问题;
20.5.结构体作为函数参数
- 值传递 和 地址传递(修改了形参,实参会跟着改变);
20.6.结构体const使用场景
- 补充:将函数中的形参改为指针,可以减少内存空间,而且不会复制新的副本出来;
- 加了const可以限制修改;
- 作用:加const可以防止函数体中的误操作;
20.7.通讯管理系统案例
- 已实践
21.内存模型
21.1.代码区
-
存放函数体的二进制代码,(程序结束)由操作系统进行管理
-
代码运行前的区域
21.2.全局区
-
存放全局变量、静态变量和常量
-
代码运行前的区域
-
局部变量、局部常量不在全局区中
21.3.栈区
- 由编译器自动分配释放,存放函数的参数值,局部变量等
- 注意事项:不要返回局部变量的地址,结合案例(需要总结 & 记住)
#include<iostream>
using namespace std;
int* func() {
int a = 10;//局部变量,存放在栈区,栈区的数据在函数执行完之后自动释放
return &a; //返回局部变量的地址
}
int main() {
int* p = func();
cout << *p << endl;//第一次可以打印正确的数字,是因为编译器做了保留
cout << *p << endl;//第二次这个数据就不再保留了
system("pause");
return 0;
}
21.4.堆区
- 由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
- 主要:利用new在堆区开辟内存
21.5.内存四区的意义
- 不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
22.引用
-
作用:给变量取别名
-
注意事项:引用必须要初始化,引用初始化之后就不可以更改
-
引用做函数参数
- 作用:形参会修改实参
- 优点:简化指针修改实参
-
引用做函数的返回值
- 不要返回局部变量的引用
- 函数的调用可以作为左值(如果函数的返回值是引用,这个函数调用可以做为左值)
#include<iostream>
using namespace std;
int& test01() {
int a = 10;
return a;
}
int& test02() {
static int a = 20;
return a;
}
int main() {
int& ref = test01();//不能返回局部变量的引用
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
int& ref2 = test02();//如果函数作为左值,那么必须返回引用
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
system("pause");
return 0;
}
-
引用的本质
- 在C++内部是指针常量
-
C++推荐使用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了;
-
常量引用
- 修饰形参,防止误操作
23.函数-高级
23.1.默认参数
- 声明和实现只能有一个默认参数
23.2.占位参数
- 占位参数调用时必须补全;占位参数可以有默认值;
23.3.函数重载
- 注意事项:
- 1.引用可以做为重载条件
- 2.函数重载碰到默认参数,会出现二义性(不调用之前可以运行成功,调用之后就会出现报错)
#include<iostream>
using namespace std;
void func(int& a) {
cout << "func (int &a) 调用 " << endl;
}
void func(const int& a) {
cout << "func (const int &a) 调用 " << endl;
}
void func2(int a, int b = 10) {
cout << "func2(int a, int b = 10) 调用 " << endl;
}
void func2(int a) {
cout << "func2(int a) 调用 " << endl;
}
int main() {
int a = 10;
func(a);//调用无const
func(10);//调用有const
//func2(10);//存在二义性
func2(10, 20);//不存在二义性
return 0;
}
24.类和对象
- C++面向对象三大特征:封装、继承、多态
- 万事万物都皆为对象,对象上有属性和行为
24.1.封装
- 意义:将属性和行为作为一个整体,表现生活的事物;将属性和行为加以权限控制;
- struct和class的区别:唯一的区别,默认的访问权限不同(前者默认public,后者默认private);
24.2.构造函数和析构函数
24.3.构造函数的分类
- 按照参数分类:无参函数、有参函数
- 按照类型分类:普通函数、拷贝函数
- 构造函数的调用:实践
#include<iostream>
using namespace std;
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;
}
private:
int age;
};
//2、构造函数的调用
//调用无参构造函数
void test01() {
Person p;//调用无参构造函数
}
//调用有参构造函数
void test02() {
//2.1. 括号法,常用
Person p1(10);
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明;
//如:
// Person p2();
//2.2.显示法
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10)单独写就是匿名对象,当前行结束之后,马上析构
//2.3.隐式转换法
Person p4 = 10; //Person p4 = Person(10);
Person p5 = p4; //Person p5 = Person(p4);
//注意2:不能利用 拷贝构造函数 初始化匿名对象,编译器认为是对象声明
//Person p5(p4);
}
int main() {
test01();
test02();
system("pause");
return 0;
}
24.4.拷贝构造函数的调用时机
- 1、使用一个已经创建完毕的对象来初始化一个新对象
- 2、值传递的方式给函数参数传值
- 3、值方式返回局部对象
#include<iostream>
using namespace std;
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;
}
private:
int age;
};
//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person p1(20);
Person p2(p1);
}
void doWork(Person p) {
}
//2、值传递的方式给函数参数传值
void test02() {
Person p;
doWork(p);
}
Person doWork2() {
Person p1;
return p1;
}
//3、值方式返回局部对象
void test03() {
Person p = doWork2();//doWork2的p1地址会跟p的地址不同;
}
int main() {
//test01();
//test02();
test03();
system("pause");
return 0;
}
24.5.构造函数的调用规则
- 如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造;
- 如果用户定义拷贝构造函数,c++不会再提供其它构造函数;
24.6.浅拷贝与深拷贝
-
浅拷贝:简单的复制拷贝操作;
-
深拷贝:在堆区重新申请空间,进行拷贝操作;
-
浅拷贝带来的问题:堆区内存的重复释放;
24.7.初始化列表
24.8.类对象作为类成员
- 类成员的对象构造先执行
- 析构函数的执行顺序是跟构造函数的执行顺序相反
24.9.类对象-静态成员
- 类内声明,类外初始化必须的做,否则不能访问其内存
- 所有对象都共享同一份数据
- 编译阶段就分配内存
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
static int m_A;//类内声明
};
int Person::m_A = 100;//类外初始化
int main() {
Person p;
cout << p.m_A << endl;
system("pause");
return 0;
}
24.10.类对象-静态成员函数
-
静态成员函数
-
所有对象共享同一个函数
-
静态成员函数只能访问静态成员变量
-
注意事项:
- 空对象占用内存空间为1
- 非静态成员变量,属于类的对象上;
- 静态成员变量,不属于类对象上;
- 非静态成员函数,不属于类对象上;
- 非静态成员函数,不属于类对象上;
24.11.类对象-this指针的用途
-
this指向被调用的成员函数所属的对象
-
1.解决命名冲突
-
2.返回对象本身用*this;
-
代码(注意personAddAge返回值的不同时所存在的差异)
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
Person(int age) {
this->age = age;
}
//如果返回值为Person,即值的方式,那么就会返回新的对象
//如果返回值为Person&,那么就会返回对象本身
Person personAddAge(Person& p) {
this->age += p.age;
return *this;
}
int age;
};
void test01() {
Person p1(18);
cout << "p1的年龄为: " << p1.age << endl;
}
void test02() {
Person p1(10);
Person p2(10);
p2.personAddAge(p1).personAddAge(p1).personAddAge(p1);
cout << "p2的年龄为: " << p2.age << endl;
}
int main() {
test02();
system("pause");
return 0;
}
24.12.类对象-空指针访问成员函数
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
void showClassName() {
cout << "this is Person class " << endl;
}
void showPersonAge() {
cout << "age = " << m_Age << endl;//m_Age 前面隐式包含 this
}
int m_Age;
};
void test01() {
Person* p = NULL;
p->showClassName();//不报错
//p->showPersonAge();//存在报错
}
int main() {
test01();
system("pause");
return 0;
}
24.13.类对象-const修饰成员函数
- this的本质是指针常量,在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改;但是若增加了mutable修饰,依旧可以修改;
- 常对象只能调用常函数
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
void showClassName() const{
this->m_Age = 10;//不可以修改
}
int m_Age;
};
int main() {
system("pause");
return 0;
}
24.14.类对象-友元
-
目的:让一个函数或类访问另一个类中私有成员
-
全局函数做友元
#include<iostream>
#include<string>
using namespace std;
class Building
{
friend void goodGay(Building* building);
public:
Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
void goodGay(Building* building) {
cout << "好基友全局函数 正在访问: " << building->m_SittingRoom << endl;
cout << "好基友全局函数 正在访问: " << building->m_BedRoom << endl;
}
void test01() {
Building building;
goodGay(&building);
}
int main() {
test01();
system("pause");
return 0;
}
- 类做友元
#include<iostream>
#include<string>
using namespace std;
class Building;
class GoodGay {
public:
GoodGay();
void visit();
Building* building;
};
class Building
{
friend class GoodGay;//增加
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building() {
m_SittingRoom = "客厅";
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;
}
- 成员函数做友元
#include<iostream>
#include<string>
using namespace std;
class Building;
class GoodGay {
public:
GoodGay();
void visit();
void visit2();
Building* building;
};
class Building
{
//告诉编译器,goodGay类中的visit成员函数是Building好朋友,可以访问私有内容
friend void GoodGay::visit();
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay() {
building = new Building;
}
void GoodGay::visit() {
cout << "好基友类正在访问:" << building->m_BedRoom << endl;
}
void GoodGay::visit2() {
cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
}
void test01() {
GoodGay gg;
gg.visit();
gg.visit2();
}
int main() {
test01();
system("pause");
return 0;
}
24.15.运算符重载
- 对简化的写法做了详细的解释,通俗易懂
- 运算符重载也可以发生函数重载
- 成员函数重载/全局函数重载
24.15.1.加号运算符
//成员函数重载本质调用
Person p3 = p1.operator+(p2);
//全局函数重载本质调用
Person p3 = operator+(p1,p2);
- 注意:对于内置的数据类型的表达式的运算符不可能改变的;不要滥用运算符重载;
24.15.2.左移运算符
#include<iostream>
using namespace std;
class Person {
friend ostream& operator<<(ostream& cout, Person& p);
public:
Person(int a, int b) {
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
};
//本质 operator<<(cout,p),简化写法 cout << p
ostream& operator<<(ostream& cout, Person& p) {
cout << "m_A = " << p.m_A << "m_B = " << p.m_B;
return cout;
}
void test01() {
Person p(10,10);
/*p.m_A = 10;
p.m_B = 10;*/
cout << p << endl;
}
int main() {
test01();
system("pause");
return 0;
}
24.15.3.递增运算符
- 如何区分递增在前和递增在后(暂时记忆吧)
//cout << ++myInt << endl;编译器会将其解析为:myInt.operator++(); // 调用前置自增运算符
//cout << myInt++ << endl;编译器会将其解析为:myInt.operator++(0); // 调用后置自增运算符,参数 0 是占位符
//编译器根据语法规则来判断是前置还是后置自增:
//如果 ++ 出现在操作数之前(如 ++myInt),则调用 前置自增运算符 operator++()。
//如果 ++ 出现在操作数之后(如 myInt++),则调用 后置自增运算符 operator++(int)
#include<iostream>
using namespace std;
class MyInteger {
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger() {
m_Num = 0;
}
//前置++
MyInteger& operator++() {
//先+++,再返回
m_Num++;
return *this;
}
//后置++
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;
}
int main() {
test01();
system("pause");
return 0;
}
24.15.4.赋值运算符
#include<iostream>
using namespace std;
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(19);
Person p3(20);
p1 = p2 = p3;
cout << *p1.m_Age << endl;
cout << *p2.m_Age << endl;
cout << *p3.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
24.15.5.关系运算运算符
- 略
24.15.6.函数调用运算符
#include<iostream>
using namespace std;
class MyAdd
{
public:
int operator()(int v1, int v2) {
return v1 + v2;
}
};
void test01() {
MyAdd add;
int ret = add(10, 10);
cout << "ret = " << ret << endl;
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
int main() {
test01();
system("pause");
return 0;
}
24.16.继承
-
重点:非难点;
-
父类的私有成员也被子类继承下去了,只是由编译器隐藏访问不到;
24.16.1.继承的构造和析构的顺序
- 父构造、子构造、子析构、父析构;
24.16.2.同名成员的处理
- 子类出现与父类同名的成员,编译器会隐藏父类所有的同名成员;
- 通过子类对象访问父类对象的同名成员/函数,增加作用域;
- 同名静态成员(通过对象/类型):增加作用域;
24.16.3.多继承
- 当父类出现同名成员,存在二义性,增加作用域处理;
- 实际开发中,不建议使用多继承;
24.16.4.菱形/钻石继承
-
利用虚继承可以解决菱形继承的问题
-
开发中不建议写改类方式的代码,但是若看到有这样的写法要能看明白
-
vbptr:虚基类指针,继承指针,指针通过偏移量找到变量;
24.17.多态
-
静态多态:函数重载/运算符重载,编译阶段确定函数地址
-
动态多态:派生类/虚函数实现,运行阶段确定函数地址
-
动态多态满足条件
- 1.有继承关系
- 2.子类重写父类的虚函数
-
动态多态的使用
- 父类的指针或引用执行子类对象
24.17.1.多态的原理
- 父类定义了虚函数,子类进行了重写
- 父类的内部结构,会存在虚函数指针和虚函数表,表会记录虚函数的地址,子类继承父类时,会继承父类的虚函数指针和虚函数表,当子类重写父类的虚函数,子类的虚函数表,内部会替换成子类的虚函数地址
24.17.2.多态的好处
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的拓展及维护
24.17.3.纯虚函数
-
语法:virtual 返回值类型 函数名 (参数列表) = 0
-
当类中有了纯虚函数,这个类也称为抽象类
-
抽象类特点
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
24.17.4.虚析构和纯虚析构
- 两者特点:解决父类指针释放子类对象
- 都需要有具体的函数实现
- 纯虚析构:既需要声明,且需要实现;
24.18.文件操作
- 通过文件将数据持久化
- 分类:文本文件(ASCII码形式)/二进制文件
//二进制文件-写文件
#include<fstream>
#include<string>
using namespace std;
class Person {
public:
char m_Name[64];
int m_Age;
};
void test01() {
//1、包含头文件
//2、创建输出流对象
ofstream ofs("person.txt",ios::out | ios::binary);
//3、打开文件
//4、写文件
Person p = { "张三",18 };
//&p 表示获取对象 p 的地址。p 是一个 Person 类型的对象,因此 &p 的类型是 Person*,即指向 Person 类型的指针。
//来自deepseek,待验证
//(const char*)&p 是一种 C-style cast,它将 Person* 类型的指针强制转换为 const char* 类型的指针。这种转换是合法的,因为 C++ 允许将任何指针类型转换为 char* 或 const char*。
ofs.write((const char*)&p, sizeof(p));
//5、关闭流
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
24.19.职工管理系统
- done
25.模板
- 泛型编程:通过模板来实现
- 模板:通用的模具,大大提高复用性
25.1.函数模板
- 建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
- 语法
template[固定的关键字] <typename T>//typename可以替换成class
函数声明或定义
- 注释事项
- 必须推导出一直的数据类型T才能使用
- 模板要确定T的类型才能使用
template<class T>
void func() {
cout << "func调用" << endl;
}
void test02() {
func<int>();//不增加<int>则不能调用func
}
-
普通函数和函数模板
- 普通函数在调用时可以发生自动类型转换(隐式类型转换);
- 函数模板在调用时,如果用自动类型推导,不会发生隐式类型转换;
- 如果利用显示指定类型的方式,可以发生隐式类型转换;
- 建议使用显示执行类型的方式调用函数模板(因为可以自己确定通用类型T);
-
普通函数和函数模板的调用规则
- 普通函数和函数模板是可以发生重载的
- 如果普通函数和函数模板都可以实现,优先调用普通函数(强制调用除外)
- 可以通过空模板参数列表 强制 调用函数模板
- 如果函数模板可以产生更好地匹配,优先调用函数模板
#include<iostream>
using namespace std;
template<class T>
void myPrint(T a,T b) {
cout << "调用的函数模板" << endl;
}
void myPrint(int a, int b) {
cout << "调用的普通函数" << endl;
}
void test01() {
int a = 10;
int b = 20;
myPrint<>(a, b); //空模板参数列表
}
int main() {
test01();
system("pause");
return 0;
}
- 模板的局限性
- 模板不是万能的,有些特定数据类型,需要用具体化方式做特殊实现
#include<iostream>
using namespace std;
#include<string>
class Person {
public:
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
template<class T>
bool myCompare(T &a, T &b) {
if (a == b) {
return true;
}
else {
return false;
}
}
//具体化Person的版本实现代码,具体化优先调用
template<> bool myCompare(Person& p1, Person& p2) {
if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name) {
return true;
}
else {
return false;
}
}
void test01() {
Person p1("张三", 10);
Person p2("李四", 10);
bool ret = myCompare(p1, p2);
if (ret) {
cout << "p1 == p2" << endl;
}
else {
cout << "p1 != p2" << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
25.2.类模板
- 创建一个通用类,类的成员数据类型可以不具体制定,用一个虚拟的类型来代表;
template[固定的关键字] <typename T>//typename可以替换成class
类定义
-
类模板和函数模板的区别
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
-
类模板中成员函数创建时机
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
#include<iostream>
using namespace std;
#include<string>
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 tet01() {
MyClass<Person1> m;
m.fun1();
//m.fun2();//编译会出错,说明函数调用才回去创建成员函数
}
int main() {
tet01();
system("pause");
return 0;
}
- 类模板对象做函数参数
- 指定参数类型(使用比较广泛的方式)
- 参数模板化
- 整个类模板化
#include<iostream>
using namespace std;
template<class T1,class T2>
class Person {
public:
Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout << "姓名:" << this->m_Name << " 年龄: " << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//1.类模板的对象作为函数参数
void printPerson1(Person<string, int> &p) {
p.showPerson();
}
void test01() {
Person<string, int>p("孙悟空",100);
printPerson1(p);
}
//2.参数模板化
template<class T1,class T2>
void printPerson2(Person<T1, T2>& p) {
p.showPerson();
}
void test02() {
Person<string, int>p("二师兄", 100);
printPerson2(p);
}
//3.整个类模板化
template<class T>
void printPerson3(T& p) {
p.showPerson();
}
void test03() {
Person<string, int>p("唐僧", 100);
printPerson3(p);
}
int main() {
test03();
system("pause");
return 0;
}
- 类模板与继承
- 如果父类是类模板,子类需要指定出父类中T的数据类型
#include<iostream>
using namespace std;
template<class T>
class Base {
public:
T m;
};
//必须要知道父类的T类型,才能继承给子类
class Son :public Base<int> {
Son2<int, char> S2;
};
//如果想灵活指定父类中T类型,子类也需要变类模板
template<class T1,class T2>
class Son2 :public Base<T2> {
T1 obj;
};
int main() {
system("pause");
return 0;
}
- 类模板函数类外实现
- 需要加上模板参数列表
#include<iostream>
using namespace std;
#include<string>
template<class T1,class T2>
class Person {
public:
Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout << "姓名:" << this->m_Name << "年龄: " << this->m_Age << endl;
}
T1 m_Name;;
T2 m_Age;
};
//构造函数的类外实现
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
}
//成员函数类外实现
template<class T1, class T2>
void Person<T1,T2>::showPerson() {
}
int main() {
system("pause");
return 0;
}
-
类模板分文件编写
- 问题的产生:无法解析外部文件(因为类模板中的成员函数创建时机在调用阶段,导致分文件时链接不到)
- 解决方案,其一(一般不会这么做):直接包含源码;其二(建议方案):将.h和.cpp写在一起,将后缀名改为.hpp文件(.hpp是约定的名称,不是强制);
-
类模板与友元
- 全局函数类内实现:直接在类内声明友元即可;
- 全局函数类外实现:需要提前让编译器知道全局函数的存在;
#include<iostream>
using namespace std;
#include<string>
//让编译器知道Person的存在
template<class T1,class T2>
class Person;
//全局函数,不需要加作用域(Person::) // 函数模板的实现
template<class T1, class T2>
void printPerson2(Person<T1, T2> p) {
cout << "类外实现 --- 姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}
template<class T1,class T2>
class Person {
//1.全局函数-类内实现
friend void printPerson(Person<T1,T2> p) {
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}
//2.全局函数类外实现 & 需要让编译器提前知道改函数的存在
//friend void printPerson2(Person<T1, T2> p);//普通函数的声明
friend void printPerson2<>(Person<T1, T2> p);//增加空模板参数列表
public:
Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
void test01() {
Person<string, int> p("Tom", 20);
printPerson(p);
}
void test02() {
Person<string, int> p("Tom2", 20);
printPerson2(p);
}
int main() {
test01();
test02();
system("pause");
return 0;
}
25.3.STL
25.3.1.STL六大组件(面试可能会考察),大体划分
- 容器、算法、迭代器、仿函数、适配器、空间配置器;
26.容器
26.1.vector-存放内置数据类型容器
- vector:和数组很类似,也称为单端数组,同数组的区别,数组是静态空间,vector可以动态拓展;
26.2.string容器
- C++的风格,本质上是一个类,内部维护了char* 数组;
- 构造函数
#include<iostream>
using namespace std;
#include<string>
/*
* string() 创建一个空的字符串
* string(const string& str) 使用字符串s初始化
* string(const string& str) 使用一个string对象初始化另一个string对象
* string(int n,char c) 使用n个字符串c初始化
*/
int main() {
string s1;
const char* str = "hello world";
string s2(str);
cout << "s2 = " << s2 << endl;
string s3(s2);
cout << "s3 = " << s3 << endl;
string s4(10, 'a');
cout << "s4 = " << s4 << endl;
system("pause");
return 0;
}
- 赋值操作
- 字符串拼接
- 字符串查找和替换
- 字符串比较(按照字符串的ASCII进行比较)
- 字符串存取
- 字符串插入与删除
- 字串获取
26.3.vector构造
- 支持随机访问
- 构造
- 默认构造
- 区间构造
- n个element构造
- 拷贝构造(常用)
26.4.vector赋值操作
- =号/区间/n个element
26.5.vector容量和大小
- empty/size/capacity/resize
26.6.vector插入和删除
26.7.vector数据存取
- at/索引/front(第一个元素)/back(最后一个元素)
26.8.vector互换容器
- swap
- 巧用swap可以收缩内存空间
26.9.vector预留空间
- reserve(int len)
26.10.deque构造函数
- vector和deque的区别:
- vector对于头部的插入效率低,数据量越大,效率越低;
- deque相比vector而言,对头部的插入和删除比vector快;
- vector访问元素时的速度会比deque快;
26.11.deque赋值、大小、插入、删除、存取、排序
26.12.stack容器
26.13.queue容器
26.14.list容器
- list和vector是两个最常被使用的容器
- list本质是链表,非连续性空间存储数据
26.14.1.list构造
- 普通构造/区间/拷贝/n个element
26.14.2.list赋值和交换
26.14.3.list大小操作
26.14.4.list插入和删除
26.14.5.list数据存取
- front/back
26.14.5.list反转和排序
- reverse/sort(可以传入比较器)
26.15.set容器
-
关联式容器,底层是二叉树实现;
-
插入和删除
-
拷贝构造
-
大小和交换:size()/empty()/swap(set)
-
查找和统计:find(key)-返回的是迭代器/count(key)
-
set和multiset区别:前者不可以插入重复数据,后者可以插入重复数据
26.16.map容器
- 复制、大小和交换、插入和删除、查找和统计、排序
26.17.函数对象基本使用
-
函数对象(仿函数)是一个类,不是一个函数
-
特点:函数对象调用时,可以像普通函数那样,可以有参数,可以有返回值;函数对象可以作为参数传递;函数对象超出普通函数的概念,函数对象可有自己的状态;
-
谓词:仿函数返回值类型为bool数据类型,称为谓词
-
一元谓词:如果operator()接受了一个参数,那么叫做一元谓词
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
class GreaterFive {
public:
bool operator()(int val) {
return val > 5;
}
};
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//参数3:匿名函数对象
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
if (it == v.end()) {
cout << "未找到" << endl;
}
else {
cout << "找到了大于5的数字为:"<< *it << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
-
二元谓词:如果operator()接受了两个参数,那么叫做二元谓词
-
内建函数对象:STL内建了一些函数对象
- 算术仿函数:加/减/乘/除
- 关系仿函数:等于/不等于/大于(最常用)/不等于/小于/小于等于
- 逻辑仿函数
27.STL(常用)算法
27.1.遍历
- for_each:遍历容器
- transform:搬运容器到另一个容器中
27.2.查找
- find:按值查找元素,找到返回指定元素的迭代器,找不到返回结束迭代器end;
- find_if:按条件查找元素,找到返回指定元素的迭代器,找不到返回结束迭代器end;
- adjacent_find:查找相邻重复元素,返回相邻元素的第一个位置的迭代器。面试题若出现相邻重复元素,记得使用STL中的adjacent_find;
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
void test01() {
vector<int> v;
v.push_back(0);
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(1);
v.push_back(4);
v.push_back(5);
v.push_back(5);
vector<int>::iterator pos = adjacent_find(v.begin(), v.end());
if (pos != v.end()) {
cout << "找到了" << endl;
}
else {
cout << "未找到" << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
- binary_search:查找指定元素是否存在,查找到了就返回true,没找到就返回false;注意:在无需序列中不可用;
- count:统计元素出现的次数;对于自定义类型需要对==号进行重载;
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
class Person {
public:
Person(string name,int age) {
this->m_Name = name;
this->m_Age = age;
}
bool operator==(const Person& p) {
return this->m_Age == p.m_Age;
}
string m_Name;
int m_Age;
};
void test01() {
vector<Person> v;
Person p1("刘备", 35);
Person p2("关羽", 30);
Person p3("张飞", 29);
Person p4("赵云", 25);
Person p5("黄忠", 50);
Person p6("马超", 22);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
v.push_back(p6);
Person p7("诸葛亮", 25);
int num = count(v.begin(), v.end(), p7);
cout << "num = " << num << endl;
}
int main() {
test01();
system("pause");
return 0;
}
- count_if:按条件统计元素出现的次数,返回次数;
27.3.排序
- sort:对容器内元素进行排序
- sort属于开发中最常用的算法之一,需熟练掌握
#include<iostream>
using namespace std;
#include<algorithm>
#include<vector>
void myPrint(int val) {
cout << val << " ";
}
void test01() {
vector<int> v;
v.push_back(10);
v.push_back(50);
v.push_back(30);
v.push_back(20);
v.push_back(60);
sort(v.begin(), v.end());//sort默认是从小到大排序
for_each(v.begin(), v.end(), myPrint);
cout << endl;
sort(v.begin(), v.end(), greater<int>());//从大到小排序
for_each(v.begin(), v.end(), myPrint);
cout << endl;
}
int main() {
test01();
system("pause");
return 0;
}
- random_shuffle:指定范围内的元素随机调整次序(利用洗牌算法,打乱顺序。该算法比较实用,使用时记得加随机算法种子)
- merge:容器元素合并,并存储到另一个容器中
- 注意:两个容器必须是有序的
- reverse:反转指定范围的元素(目标:面试中若到该算法,需要能快速想起来)
27.4.拷贝
- copy:容器内指定范围的元素拷贝到另一个容器中,参数(1,起始,2,结束,3,起始)
27.5.替换
- replace:将容器内指定范围的旧元素修改为新元素,参数(1,起始,2,结束,3,旧,4,新)
- replace_if:容器内指定范围满足条件的元素替换为新元素,参数(1,起始,2,结束,3,谓词,4,新)
- swap:互换两个容器的元素,参数(1,容器一,2,容器二)
27.6.算数生成
- accumulate:计算区间内容器元素累计总和(很实用的算法),参数(1,起始,2,结束,3,起始值)
- fill:向容器指定区间添加元素,参数(1,起始,2,结束,3,填充的值)
27.7.集合
-
set_intersection:求两个容器的交集,参数(1,起始一,2,结束一,3,起始二,4,结束二,5,起始三)
- 求交集的两个集合必须是有序序列
- 目标容器开辟空间需要从两个容器中取最小值
- set_intersection,返回值既是交集中最后一个元素的位置
-
set_union:求两个容器的并集,参数同上
- 两个集合必须是有序序列
-
set_difference:求两个容器的差集,参数同上
- 两个集合必须是有序序列
28.演讲比赛流程管理系统
- done
29.机房预约系统
- Visual Studio C/C++代码读取文件出现乱码(文件保存时默认UTF-8,而控制台的编码方式为ANSI)
- Identity*& manager 表示:manager 是一个指向 Identity 类对象的指针的引用。通过这个引用,可以直接修改传入的指针本身(而不仅仅是修改指针指向的对象);
- done
评论区