C++ 复习

VS 修改 C++ 语言标准

在这里插入图片描述

右键项目-属性

输入输出

//引用头文件,用<>包裹起来的一般是系统提供的写好的代码 编译器会在专门的系统路径中去进行查找
#include <iostream>
//自己写的代码文件一般都用""包裹起来 编译器会在当前文件所在的目录中査找,找不到才会去系统路径重查找
//#include "xxx"int main()
{// std:: 命名空间// cout  该命名空间下的一个对象// <<    是插入运算符,将字符串插入到 std::cout 对象中,以便输出到控制台std::cout << "Hello World!\n";//换行std::cout << "等待输入" << std::endl;//声明一装载输入内容的容器std::string input;//等待玩家输入,必须输入一些内容后回车才认为结束std::cin >> input;//主函数可以省略//return 0;
}

命名空间

#include <iostream>
//引用命名空间
using namespace std;int main()
{cout << "Hello World" << endl;
}

变量

#include <iostream>
using namespace std;//外部变量,允许不同文件访问的全局变量,生命周期是整个程序运行期
extern int GlobeInt = 30;
//外部函数
extern void Test();int main()
{
#pragma region MyRegion//折叠代码
#pragma endregion//long 类型范围 -21亿 ~ 21亿 或 -9223亿亿 ~ 9223亿亿//不同操作系统(windows,UNIX,Linux)上所占的空间不同//windows 上 long 和 int 的存储空间一样long l = 10L;cout << "长整型: " << l << endl;cout << sizeof(int) << " " << sizeof(long) << endl;//无符号unsigned long ul = 20UL;cout << ul << endl;//范围 -9223亿亿 ~ 9223亿亿long long ll = 200000000000LL;cout << ll << endl;//6~7位有效数字float f = 1.5f;cout << f << endl;//15~16位有效数字double d = 4.6;cout << d << endl;//有效数字 18~21,或 33 ~ 36,不同操作系统不同long double ld = 1.8L;cout << ld << endl;//R"(内容)",包裹不想被转义的字符串string str = R"(""\""\n)";cout << str << endl;char c = 'A';//两个 char 相加,会被转为 int 进行加法运算,要避免这种写法str = c + "Q";cout << str << endl;//自动变量,让编辑器自动推导变量类型,生命周期和普通变量一样auto i = 10;//静态变量,程序整个生命周期内一直存在,局部静态变量也会一直存在static int j = 20;
}

字符串操作

#include <iostream>
#include <string>using namespace std;int main()
{//其他类型转字符串string str = to_string(123);str.append("456");cout << str << endl;//字符串转其他类型string str2 = "234";int i = stoi(str2);cout << i << endl;long l = stol(str2);cout << l << endl;unsigned long ul = stoul(str2);cout << ul << endl;
}

异常捕获

#include <iostream>
#include <string>using namespace std;int main()
{try {int i2 = stoi("测试");}catch (const exception& e){cout << e.what() << endl;}
}

数组

一维数组

#include <iostream>
using namespace std;int main()
{//3种数组定义方式int arr[5];arr[0] = 1;//赋值之前可能是任意数cout << arr[0] << endl;int arr2[5] = { 0, 1, 2, 3, 4 };cout << arr2[2] << endl;int arr3[] = { 0, 1, 2, 3, 4 };cout << arr3[3] << endl;//获取数组元素个数int count = sizeof(arr) / sizeof(arr[0]);cout << count << endl;//数组首地址,16进制cout << arr << endl;//转为10进制cout << (int)arr << endl;//数组第一个元素地址cout << &arr[0] << endl;//数组名是常量,不可以进行赋值操作//arr = 100;
}

二维数组

#include <iostream>
using namespace std;int main()
{//4种定义二位数组的方式//数组名[行数][列数]int arr[2][3];arr[0][0] = 1;cout << arr[0][0] << endl;int arr2[2][3] = { {1,2,3}, {4,5,6} };cout << arr2[0][1] << endl;int arr3[2][3] = { 1,2,3,4,5,6 };cout << arr2[0][2] << endl;int arr4[][3] = { 1,2,3,4,5,6 };cout << arr4[1][0] << endl;
}

函数

函数声明

#include <iostream>
using namespace std;//函数声明,告诉编译器有这么个函数,写在 main 函数之后的函数需要声明
int Max(int a, int b);int main()
{int c = Max(1, 2);cout << c << endl;
}int Max(int a, int b) 
{return a > b ? a : b;
}

函数分文件编写
创建后缀名为 .h 的头文件,在头文件中写函数的声明
创建后缀名为 .cpp 的源文件,在源文件中写函数的定义

创建 Max.h 头文件,头文件里尽量少 include 其他头文件,不要使用 using namespace,防止代代相传

int Max(int a, int b);

创建 Max.cpp 源文件

#include "Max.h"int Max(int a, int b)
{return a > b ? a : b;
}

其他文件中调用

#include <iostream>
#include "Max.h"using namespace std;int main()
{int c = Max(1, 2);cout << c << endl;
}

inline 内联函数

调用内联函数时,直接将代码插入到调用处,而不是执行常规的函数调用,目的是减少函数调用开销,内联函数调用会导致代码膨胀,所以适合一些小的频繁调用的函数

inline int Add(int a, int b)
{return a + b;
}

指针

#include <iostream>
using namespace std;int main()
{int a = 10;//取地址int * p = &a;cout << p << endl;//解引用,找到指针指向内存中的数据cout << *p << endl;*p = 20;cout << a << " " << *p << endl;//32位下4字节,64位下8字节,不管什么类型的指针cout << sizeof(p) << endl;
}

空指针

#include <iostream>
using namespace std;void Fun(int * p) 
{cout << "指针" << endl;
}void Fun(int p)
{cout << "整数" << endl;
}int main()
{//空指针用于给变量初始化int * p = NULL;//空指针不能访问//*p = 10;//输出为整数,违反直觉Fun(NULL);
}

在这里插入图片描述
查看 NULL 的定义,void * 是无类型指针或通用指针。它可以指向任何类型的数据,但在解引用之前需要将其转换为具体的类型。
当 NULL 被用作指针时,它可能会被隐式转换为整数类型,这可能导致难以发现的错误。

C++11 引入了 nullptr 关键字,专门用来区分空指针和 0,替代 NULL

int * p = nullptr;

野指针

指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针

出现野指针的常见操作:

  • 指针声明之后没有初始化
  • 变量内存释放后,指针变量还保存着该变量的内存地址

常量指针,指针常量

常量指针:const 在 * 之前,指向的值不能修改(不能通过解引用修改值),但指向可以改变。可以理解为常量的指针,指向常量的指针,const 限定了 * ,那么 * 操作不能用

int a = 1;
int b = 2;
const int * p = &a;
p = &b;		//指向可以修改
*p = 3;		//错误,指向值不能修改
b = 4;		//可以修改原始变量值

指针常量:const 在 * 之后,指向的值可以修改,但指向不能改变。可以理解为指针类型的常量,const 限定了指针,那么指针不能操作

int a = 1;
int b = 2;
int * const p = &a;
p = &b;		//错误,指向不能修改
*p = 3;		//指向的值可以修改

const 修饰指针和常量,指向和值都不能修改

int a = 1;
const int* const p = &a;

指针访问数组

int arr[5] = { 1,2,3,4,5 };
//数组名是首地址
int * p = arr;
cout << "第一个元素:" << *p << endl;for (int i = 0; i < 5; i++)
{cout << *p << endl;p++;	//向后偏移一个类型长度
}

结构体

定义

struct Student
{string name;int age;
}s3;	//在定义结构体的时候创建变量 s3//函数参数改为指针,实现引用传递,避免值传递复制新的副本,减少内存空间
//常量指针,防止修改值
void PrintStu(const Student* s) 
{s->age = 20; //错误,不能修改cout << s->name << endl;
}int main()
{Student s1;s1.name = "张三";s1.age = 10;Student s2 = { "李四", 20 };s3.name = "王五";Student s4[2] ={{"张三", 10},{"李四", 20},};//指针操作Student * p = &s2;cout << p->name << p->age << endl;PrintStu(&s3);
}

内存四区

代码区存放函数体的二进制代码,特点是共享和只读
全局区存放全局变量,静态变量以及常量(字符串常量和 const 修饰的全局常量),程序结束后由操作系统释放
栈区存放局部变量,函数参数,返回地址,由编译器自动分配和释放
堆区由程序员分配和释放,如不释放,程序结束后由操作系统回收

四区的意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程

new 运算符

//堆上创建整型数据,new 返回该数据类型的指针
int * p = new int(10);
//释放堆中的数据
delete p;int * p2 = new int[10];
//释放数组要加[]
delete[] p2;

引用

作用: 给变量起别名

引用和指针的区别:

  • 引用必须初始化且不能修改指向,指针可以初始化为空并可以修改指向
  • 引用没有专门的内存地址,指针有专门的内存地址
  • 指针可以进行加减运算移动指向,引用不行
int a = 10;
//语法: 数据类型 &别名 = 原名
//引用必须初始化,且初始化后不能更改
int &b = a;cout << a << " " << b << endl;	//10 10
b = 100;
cout << a << " " << b << endl; //100 100int c = 20;
//&b = c; //错误,不能更改int* p = new int(66);
int& refP = *p;
delete p;
//指针悬空后,引用也就成为悬空引用
p = nullptr;

引用做函数参数,和地址传递效果一样,语法更简单

//值传递,形参不会修饰实参
void Swap01(int a, int b)
{int temp = a;a = b;b = temp;
}//地址传递,形参会修饰实参
void Swap02(int * a, int * b)
{int temp = *a;*a = *b;*b = temp;
}//引用传递,形参会修饰实参,这里的参数是原变量的别名
void Swap03(int &a, int &b)
{int temp = a;a = b;b = temp;
}

引用作为函数返回值

//不要返回局部变量的引用,栈上会自动释放
int& Test1()
{int a = 10;return a;
}int& Test2()
{static int a = 10;	//静态变量,在全局区return a;
}int main()
{int& ref = Test2();cout << ref << endl; //10//函数调用可以作为左值Test2() = 20;cout << ref << endl; //20
}

引用的本质是 C++ 内部实现的一个指针常量

int a = 10;
//自动转换为 int * const ref = 10
int & ref = a;
//自动转换为 *ref = 20
ref = 20;

常量引用

void Test(const int & val)
{val = 20; //错误,防止修改
}int main()
{//编译器转换为 int temp = 10;  const int & ref = 10;const int & ref = 10;ref = 20; //错误,加 const 之后变成只读
}

左值:表达式结束后仍然存在的对象,通常是可以被赋值的,可以取到地址的
右值:临时对象,表达式结束后释放,没有明确的存储位置,不能获取地址,比如字面量

//x左值,右侧结果3就是一个右值
int x = 1 + 2;

左值引用,右值引用

int a = 10;
//左值引用
int& refA = a;//右值引用,延长右值的生命周期
int&& refB = 3;

类和对象

C++ 面向对象三大特征:封装,继承,多态

封装:把属性和行为作为一个整体,通过 public,protect,private 对属性和行为加以权限控制。

struct 和 class 的区别

唯一区别就是默认的访问权限不一样,struct 默认权限为 public,class 默认权限为 private

构造函数,拷贝构造,析构函数

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

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

构造函数调用规则如下:

  • 如果用户定义有参构造函数,编译器不在提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,编译器不会再提供其他构造函数
class Person
{
public://无参构造Person() {cout << "默认构造" << endl;}//有参构造Person(int a) {age = a;cout << "有参构造" << endl;}//拷贝构造函数,用于复制对象,const阻止修改原对象Person(const Person& p){//拷贝所有属性age = p.age;cout << "拷贝构造" << endl;}//析构函数,不能有参数,因此不能重载~Person(){cout << "析构" << endl;}int age;
};//值传递,会调用拷贝构造,实参传递给形参
void Test1(Person p) {}//​RVO(Return Value Optimization)返回值优化,是编译器对函数返回对象时的拷贝操作的优化
//值方式返回局部对象,RVO关闭会拷贝一个新的对象返回,调用拷贝构造,RVO开启则不会
Person Test2() 
{Person p1;return p1;
}int main()
{//调用方式//1.括号法Person p1;		//调用默认无参构造Person p2(10); //调用有参构造Person p3(p2); //调用拷贝构造,使用已经创建的对象来初始化新对象//2.显示法Person p4 = Person(10); //有参构造Person p5 = Person(p4); //拷贝构造//3.隐式转换法Person p6 = 10; //相当于 Person p6 = Person(10);Person p7 = p6; //拷贝构造Test1(p1); 	//拷贝构造Person p8 = Test2(); //可能触发拷贝构造,看编辑器是否优化
}

深拷贝,浅拷贝

浅拷贝:简单的赋值拷贝操作。问题:可能导致堆区内存重复释放
深拷贝:在堆区重新申请空间,进行拷贝操作,如果有属性在堆区开辟内存,一定要自己提供拷贝构造函数

class Person
{
public:Person(int age){//在堆上创建内存pAge = new int(age);}~Person(){if (pAge != nullptr) {delete pAge;pAge = nullptr;}cout << "析构" << endl;}int * pAge;
};int main()
{Person p1(10);Person p2(p1); //拷贝构造//p1 p2的 pAge 指向同一个内存地址,p2析构释放内存,p1析构再释放就会报错
}

初始化列表

class Person
{
public://传统初始化操作//Person(int a, int b, int c)//{//	m_a = a;//	m_b = b;//	m_c = c;//}//初始化列表,初始化属性Person(int a, int b, int c) : m_a(a), m_b(b), m_c(c){//和上面构造函数效果一样}int m_a;int m_b;int m_c;
};

类对象成员构造,析构顺序

class A{};class B 
{A a;
};

创建 B 对象时,构造顺序:先 A 后 B,销毁 B 对象时,析构顺序:先 B 后 A

静态成员变量,静态成员函数

class Person
{
public://静态函数只能访问静态变量,static void func(){m_a = 30;//m_b = 10;	//错误,不能确定m_b属于哪个对象,因此不能访问cout << "静态函数" << endl;}//静态成员变量,所有对象共享一份数据//编译阶段分配内存,类内声明static int m_a;int m_b;
};//类外初始化
int Person::m_a = 10;int main()
{Person p1;p1.m_a = 20;cout << "通过对象访问" << p1.m_a << endl;cout << "通过类名访问" << Person::m_a << endl;//通过对象调用p1.func();//通过类名调用Person::func();
}

成员变量和成员函数分开存储

class Person{};class Animal
{
public://非静态成员变量占用对象内存空间,其他的都不占用int age;
};int main()
{Person p;Animal a;//编辑器会给空对象分配一个字节空间,是为了区分空对象占内存的位置cout << sizeof(p) << endl; //1cout << sizeof(a) << endl; //4
}

this 指针

this 指针的本质是指针常量,指向不能修改

class Person
{
public:Person(int age){//this指针是隐含在每一个非静态成员函数内的指针,指向调用这个函数所属的对象this->age = age;}Person& AddAge(Person & p){this->age += p.age;//this是指向调用对象的指针,*this就是对象本体return *this;}int age;
};int main()
{Person p1(10);Person p2(10);//链式编程思想p2.AddAge(p1).AddAge(p1).AddAge(p1);cout << p2.age << endl; //40
}

常函数,常对象

class Person
{
public://常函数,const 修饰的是 this 指向,让 this 指向的值也不能修改void Test() const{//m_a = 10; //错误,常函数中不能修改普通成员变量m_b = 20;}void Test2(){}int m_a;mutable int m_b; //mutable 是可变的,常函数中也能修改
};int main()
{//常对象,不能修改成员变量const Person p;//p.m_a = 10; //错误,不能修改p.Test();       //常对象只能调用常函数//p.Test2();  //错误,不能调用普通成员函数,因为普通成员函数可以修改属性
}

友元

友元的目的是让一个函数或类访问另一个类中的私有成员
1.全局函数做友元

class Person
{//申明这个全局函数可以访问私有成员friend void GlobeFun(Person& p);
private:int m_a;
};//全局函数
void GlobeFun(Person& p)
{p.m_a = 10;
}

2.类做友元

class Person
{//申明这个类可以方法私有成员friend class FriendClass;
private:int m_a;
};class FriendClass
{
public:void Visit(){p = new Person();p->m_a = 10;}Person* p;
};

3.成员函数做友元
注意声明类和方法的顺序,否则访问不到私有成员

//前向声明依赖的类
class Person;class FriendClass
{
public://定义方法但不实现void Visit(Person& p);Person* p;
};class Person
{//申明这个类的成员函数可以方法私有成员friend void FriendClass::Visit(Person& p);private:int m_a;
};// 实现友元方法
void FriendClass::Visit(Person& p) 
{p.m_a = 10;
}

运算符重载

加法重载

class Person
{
public:int m_a;//1.成员函数重载//Person operator+(Person& p)//{//	Person temp;//	temp.m_a = this->m_a + p.m_a;//	return temp;//}
};//2.全局函数重载
Person operator+(Person& p1, Person& p2)
{Person temp;temp.m_a = p1.m_a + p2.m_a;return temp;
}int main()
{Person p1;Person p2;Person P3 = p1 + p2;
}

左移运算符重载,可以配合友元使用

class Person
{friend ostream& operator<<(ostream& cout, Person& p);public:Person(int a){m_a = a;}private:int m_a;
};//只能利用全局函数重载左移运算符
ostream& operator<<(ostream& cout, Person& p)
{cout << "m_a: " << p.m_a << endl;return cout;
}int main()
{Person p(10);cout << p << endl;
}

递增运算符重载

class MyInteger
{//前置++,返回引用,为了一直对一个数据操作MyInteger& operator++(){m_Num++;return *this;}//后置++,返回临时值,int 是个占位参数,用于区分前置和后置++MyInteger operator++(int){//先记录当前值返回,然后加1MyInteger temp = *this;m_Num++;return temp;}private:int m_Num;
};

赋值运算符重载

class Person
{
public:Person(int a){m_a = new int(a);}//重载赋值运算符Person& operator=(Person& p){//有属性在堆区,先释放,再深拷贝if (m_a != nullptr){delete m_a;m_a = nullptr;}m_a = new int(*p.m_a);return *this;}int* m_a;
};int main()
{Person p1(10);Person p2(20);Person p3(30);p3 = p2 = p1;cout << *p1.m_a << endl; //10cout << *p2.m_a << endl; //10cout << *p3.m_a << endl; //10
}

关系运算符重载(>, <, >=. <=, ==)

class Person
{
public:Person(string name, int age){m_Name = name;m_Age = age;}//重载关系运算符bool operator==(Person& p){return this->m_Name == p.m_Name && this->m_Age == p.m_Age;}string m_Name;int m_Age;
};int main()
{Person p1("Tom", 10);Person p2("Tom", 10);if (p1 == p2) {cout << "相等" << endl;}else{cout << "不相等" << endl;}
}

函数调用运算符()重载

由于重载后的使用方式非常像函数调用,因此也成为仿函数

class MyPrint
{
public://重载函数调用运算符void operator()(string test){cout << test << endl;}
};int main()
{MyPrint m;m("hello");
}

继承

减少重复代码
在这里插入图片描述

class Base
{
public:int m_A;
protected:int m_B;
private:int m_C; //私有成员也会继承下去
};class Son : public Base
{
public:int m_D;
};int main()
{//父类所有的非静态成员属性都会被子类继承,private 成员访问不到也会继承cout << sizeof(Son) << endl; //16
}

继承中的构造和析构顺序

构造顺序:先父类后子类
析构顺序:先子类后父类

多继承

实际开发中不建议使用

class Base1
{
public:Base1(){m_A = 20;}int m_A;
};class Base2
{
public:Base2(){m_A = 30;}int m_A;
};class Son : public Base1, public Base2
{
public:Son(){m_B = 10;}int m_B;
};int main()
{Son s;cout << sizeof(Son) << endl; //12//父类中出现同名成员,加作用域区分cout << s.Base1::m_A << endl; //20cout << s.Base2::m_A << endl; //30
}

菱形继承

class Animal
{
public:int m_A;
};//加 virtual 变成虚继承,公共父类为虚基类
//解决菱形继承导致的数据重复问题
class Sheep : virtual public Animal{};class Tuo : virtual public Animal{};//类内有个虚基类指针 vbptr, 指向虚基类表 vbtable,通过偏移值找到对应属性
class SheepTuo : public Sheep, public Tuo {};int main()
{SheepTuo st;st.m_A = 10;cout << st.m_A << endl;		   //10cout << st.Sheep::m_A << endl; //10cout << st.Tuo::m_A << endl;   //10
}

多态

静态多态:函数重载,运算符重载。编译阶段确定函数地址
动态多态:子类覆写虚函数。运行时确定函数地址

class Animal
{
public://虚函数virtual void Speak(){cout << "动物说话" << endl;}
};class Cat : public Animal
{
public://重写虚函数void Speak(){cout << "猫说话" << endl;}
};void DoSpeak(Animal& animal)
{animal.Speak();
}int main()
{Cat c;DoSpeak(c);cout << sizeof(Cat) << endl; //8,64位机器指针占8字节
}

原理:类内有虚函数指针 vfptr,指向虚函数表 vftable,表内记录虚函数地址

在这里插入图片描述

纯虚函数

//有纯虚函数就是抽象类,无法实例化对象
class Animal
{
public://纯虚函数,子类必须重写纯虚函数,否则也属于抽象类virtual void Speak() = 0;
};

虚析构,纯虚析构

class Animal
{
public:Animal(){cout << "Animal 构造" << endl;}//虚析构,父类指针释放子类对象时,调用子类的析构函数//virtual ~Animal()//{//	cout << "Animal 析构" << endl;//}//纯虚析构,有纯虚函数的类就是抽象类virtual ~Animal() = 0;
};//纯虚析构的实现
Animal::~Animal()
{cout << "Animal 纯虚析构" << endl;
}class Cat : public Animal
{
public:Cat(string name){m_Name = new string(name);cout << *m_Name << "Cat 构造" << endl;}~Cat(){cout << "Cat 析构" << endl;if (m_Name != nullptr){delete m_Name;m_Name = nullptr;}}string * m_Name;
};int main()
{Animal* animal = new Cat("Tom");//Animal 不是虚析构的话,不会调用子类的析构函数delete animal;
}

文件操作

文件操作三大类:

  • ofstream 写操作
  • ifstream 读操作
  • fstream 读写操作
打开方式解释
ios::in为读文件而打开文件
ios::out为写文件而打开文件
ios::ate初始位置:文件尾
ios:app追加方式写文件
ios::trunc如果文件存在先删除,再创建
ios::binary二进制方式

写文本文件

#include <iostream>
#include<fstream> //头文件
using namespace std;int main()
{//创建流对象ofstream ofs;//指定打开方式ofs.open("test.txt", ios::out);//写内容ofs << "姓名: 张三" << endl;ofs << "年龄: 10" << endl;//关闭ofs.close();
}

读文本文件

#include <string>
#include <iostream>
#include<fstream>
using namespace std;int main()
{//创建流对象ifstream ifs;//指定打开方式ifs.open("test.txt", ios::in);if (!ifs.is_open()){cout << "文件打开失败" << endl;ifs.close();return 0;}//读内容//第1种方式//char buf[1024] = { 0 };//while (ifs >> buf)//{//	cout << buf << endl;//}//第2种方式//char buf[1024] = { 0 };//while (ifs.getline(buf, sizeof(buf)))//{//	cout << buf << endl;//}//第3种方式,getline 全局函数string buf;while (getline(ifs, buf)){cout << buf << endl;}//第4种方式,不推荐//char c;//while ((c = ifs.get()) != EOF)//{//	cout << c;//}//关闭ifs.close();
}

写二进制文件

#include <string>
#include <iostream>
#include<fstream> //头文件
using namespace std;class Person
{
public:char m_Name[64];int m_Age;
};int main()
{//创建流对象fstream fs;//指定打开方式fs.open("person.txt", ios::out | ios::binary);//写内容Person p = { "张三", 20 };fs.write((const char*)&p, sizeof(p));//关闭fs.close();
}

读二进制文件

#include <string>
#include <iostream>
#include<fstream> //头文件
using namespace std;class Person
{
public:char m_Name[64];int m_Age;
};int main()
{//创建流对象ifstream ifs;//指定打开方式ifs.open("person.txt", ios::in | ios::binary);if (!ifs.is_open()){cout << "文件打开失败" << endl;ifs.close();return 0;}//写内容Person p;ifs.read((char*)&p, sizeof(p));cout << p.m_Name << " " << p.m_Age << endl;//关闭ifs.close();
}

模板

函数模板

template<typename T> //typename 可以替换成 class
void MySwap(T& a, T& b)
{T temp = a;a = b;b = temp;
}int main()
{int a = 10;int b = 20;//自动类型推导MySwap(a, b);//显示指定类型MySwap<int>(a, b);
}

普通函数与函数模板区别:

  • 普通函数调用时可以发生自动类型转换(隐式类型转换,比如 char 转 int)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换,如果利用显示指定类型的方式,可以发生隐式类型转换

模板局限性:自定义数据类型不一定能跑通

class Person
{
public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age;
};template<typename T>
bool MyCompare(T& a, T& b)
{return a == b;
}//具体化模板,解决自定义类型的通用化
template<> bool MyCompare(Person & p1, Person& p2)
{return p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age;;
}int main()
{Person p1("Tom", 10);Person p2("Tom", 10);if (MyCompare(p1, p2)){cout << "相等" << endl;}else {cout << "不相等" << endl;}
}

类模板

类模板与函数模板区别主要有两点:

  1. 类模板必须显示指定类型
  2. 类模板在模板参数列表中可以有默认参数
template<typename NameType, typename AgeType = int>
class Person
{
public:Person(NameType name, AgeType age){this->m_Name = name;this->m_Age = age;}NameType m_Name;AgeType m_Age;
};int main()
{Person<string, int> p1("张三", 10);Person<string> p2("李四", 10);  //int可以省略
}

类模板中成员函数和通类中成员函数创建时机是有区别的:

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建
template<typename T>
class MyClass
{
public://类模板中的成员函数,不是一开始就创建,而是调用时生成的void Show(){obj.Test();}T obj
};class Person
{
public:void Test(){cout << "test" << endl;}
};int main()
{MyClass<Person> m;m.Show();
}

类模板与继承

template<typename T>
class Base
{
public:T m;
};//子类继承需要指定类型,知道类型才能给子类分配内存
class Son1 : public Base<int>{};//如果想要灵活指定父类中的T类型,子类也需要变成类模板
template<typename T1, typename T2>
class Son2 : public Base<T1> 
{
public:Son2(){cout << typeid(T1).name() << endl;}T2 obj;
};

类模板分文件编写

如果类模板写在其他文件中,引用其头文件 .h 文件,链接不到模板函数的具体实现

解决办法:

  1. 直接引用源文件 .cpp,如 #include “person.cpp”,一般不这样做
  2. 将 .h 和 .cpp 文件写到一起,文件后缀改成 .hpp

创建 person.hpp 文件

#include <string>
#include <iostream>
using namespace std;template<typename T1, typename T2>
class Person
{
public:Person(T1 name, T2 age);T1 m_Name;T2 m_Age;
};template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)
{this->m_Name = name;this->m_Age = age;
}

主文件中引用

#include "person.hpp"int main()
{Person<string, int> p("Jerry", 10);
}

STL

六大组件:容器,算法,迭代器,仿函数,适配器,空间适配器

  1. 容器:各种数据结构,存储数据
    序列容器​​:元素顺序排列,如 vector, list, deque
    ​​关联容器​​:通过键(key)快速查找元素
    有序关联容器:基于红黑树实现,如 set, map
    ​​无序关联容器​​:基于哈希表实现,如 unordered_set, unordered_map。
  2. 算法:各种常用算法
  3. 迭代器:提供一种统一的方式遍历容器中的元素,容器和算法之间的胶合剂
  4. 仿函数:一个类将()重载为成员函数,行为类似函数
  5. 适配器:修改现有组件接口,使其适用于不同场景,如 stack(基于 deque 或 list 实现)
  6. 内存分配器:管理容器的内存分配和释放

vector 可变长度的数组

在这里插入图片描述

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;void MyPrint(int val)
{cout << val << " ";
}int main()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);//通过迭代器遍历元素for (vector<int>::iterator it = v.begin(); it != v.end(); it++){cout << *it << endl;}//使用 STL 提供的遍历算法for_each(v.begin(), v.end(), MyPrint);cout << v1.size() << " " << v1.capacity() << endl; //10 13//改变数组大小为n,使得 size = n,不影响 capacityv1.resize(5);cout << v1.size() << " " << v1.capacity() << endl; //5 13//数据量较大时,预分配内存,防止多次扩容v.reserve(10000);
}

互换容器

void MyPrint(vector<int>& v)
{for (vector<int>::iterator it = v.begin(); it != v.end(); it++){cout << *it << " ";}cout << endl;
}int main()
{vector<int> v1;for (int i = 0; i < 10; i++){v1.push_back(i);}vector<int> v2;for (int i = 10; i < 20; i++){v2.push_back(i);}//交互两个数组内容v1.swap(v2);MyPrint(v1);MyPrint(v2);cout << v1.size() << " " << v1.capacity() << endl; //10 13//收缩内存,vector<int>(v1) 是一个匿名对象,使用 v1 的元素初始化,// 再和 v1 交换,匿名对象自动回收vector<int>(v1).swap(v1);cout << v1.size() << " " << v1.capacity() << endl; //10 10
}

deque 双端队列

在这里插入图片描述

内部原理:

在这里插入图片描述

deque 采用分段连续的内存空间来存储元素,内部有个中控器记录每个缓冲区的地址,缓冲区中存放真实数据

void MyPrint(const deque<int>& d)
{//只读迭代器for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++){cout << *it << " ";}cout << endl;
}int main()
{deque<int> d;for (int i = 0; i < 10; i++){d.push_back(i);}MyPrint(d);
}

stack 栈

在这里插入图片描述

栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为

queue 队列

在这里插入图片描述

list 双向链表

在这里插入图片描述

pair 队组

只含有两个元素,可以看作是只有两个元素的结构体

pair<string, int> p1("zhangsan", 12);
pair<string, int> p2 = make_pair("lisi", 18);

set,multiset

插入元素后自动排序
set 不允许有重复元素,multiset 允许有重复元素

set<int> s;
s.insert(10);
s.insert(20);
s.insert(30);
set<int>::iterator pos = s.find(20);
if (pos != s.end())
{cout << "找到元素 " << *pos << endl;
}//返回队组,迭代器,bool 是否插入成功
pair<set<int>::iterator, bool> res = s.insert(40);
if (res.second) 
{cout << "插入成功 " << *res.first << endl;
}

set 默认从小到大排序,使用仿函数自定义排序规则,自定义的数据类型必须定义排序规则

class MyCompare
{
public:bool operator()(int a, int b) const{return a > b;}
};int main()
{set<int, MyCompare> s;s.insert(10);s.insert(20);s.insert(30);for (set<int, MyCompare>::iterator it = s.begin(); it != s.end(); it++){cout << *it << " ";}cout << endl;
}

map,multimap

map 中所有元素都是 pair,第一个是 key,第二个是 value,根据 key 排序

map<int, int> m;
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(3, 30));
m[2] = 20;
m[4] = 40;for (map<int, int>::iterator it = m.begin(); it != m.end(); it++)
{cout << it->first << " " << it->second << endl;
}

string

string 和 char* 区别:
char* 是一个指针。
string 是一个类,类内部封装了char*,管理这个字符串,内部分装了操作字符串的方法。

内建函数对象

#include <functional>
using namespace std;int main()
{vector<int> v;v.push_back(50);v.push_back(60);v.push_back(30);//使用内置函数对象sort(v.begin(), v.end(), greater<int>());for (vector<int>::iterator it = v.begin(); it != v.end(); it++){cout << *it << " ";}cout << endl;
}

智能指针

智能指针是一个类对象,内部封装了指针,离开作用域后调用析构函数,释放指向的内存

unique_ptr

对管理的资源具有独占性,两个 unique_ptr 不能指向同一个对象

#include <iostream>
#include <memory>
using namespace std;
class A
{
public :A(int n){this->num = new int(n);}int GetNum(){return *num;}int* num;~A(){if (num != nullptr){cout << "析构" << *num << endl;delete num;num = nullptr;}}
};int main()
{{unique_ptr<A> ptr1(new A(1));cout << *ptr1->num << endl;cout << ptr1->GetNum() << endl;}//离开作用域调用析构unique_ptr<A> ptr2 = make_unique<A>(2);//赋值空指针,调用析构ptr2 = nullptr;unique_ptr<A> ptr3 = make_unique<A>(3);//ptr3调用析构ptr3.reset(new A(4));unique_ptr<A> ptr4 = make_unique<A>(5);unique_ptr<A> ptr5 = make_unique<A>(6);//ptr5调用析构ptr5 = move(ptr4);cout << "-------" << endl;
}

输出

1
1
析构1
析构2
析构3
析构6
-------
析构5
析构4

shared_ptr,weak_ptr

多个 shared_ptr 指向同一个对象时,有共同的引用计数记录指针数量,当引用计数为 0 时,释放指向的内存

//创建对象,调用构造函数,引用+1
shared_ptr<A> ptr1 = make_shared<A>(1);
//赋值对象,引用+1
shared_ptr<A> ptr2 = ptr1;
//拷贝构造,引用+1
shared_ptr<A> ptr3(ptr2);
cout << ptr1.use_count() << endl; //3

循环引用问题:对象 A 的 shared_ptr 指向 B,B 的 shared_ptr 指向 A,形成循环引用,谁也释放不了。

weak_ptr 需要结合 shared_ptr 使用,解决循环引用问题。 weak_ptr 指向对象,只观察对象,但不增加引用计数,和 shared_ptr 之间可以相互转化,shared_ptr 可以直接赋值给它,它可以通过调用 lock 函数来获得 shared_ptr。

在这里插入图片描述

weak_ptr<A> wPtr;
{shared_ptr<A> sPtr = make_shared<A>(1);wPtr = sPtr;cout << "作用域内:" << wPtr.use_count() << endl; //1
}
cout << "作用域外:" << wPtr.use_count() << endl; //0
cout << "作用域外:" << wPtr.expired() << endl; //1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/78549.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

openGauss新特性 | HTAP新特性介绍

一、行列融合功能简介 HTAP 行列融合特性在单机、主备场景下&#xff0c;通过节点的行列双格式内存模式&#xff0c;实现openGauss HTAP一体化数据库架构。 通过高效的行列转换技术方案&#xff0c;节点读取磁盘行存数据&#xff0c;生成列存储单元&#xff08;Column Unit&am…

双目测量中的将视差图重投影成三维坐标图

双目测距主要步骤如下&#xff1a; 左右两张图片 → 匹配 → 得到视差图 disp&#xff1b; 使用 cv2.reprojectImageTo3D(disp, Q) 将视差图 重投影 成三维坐标图 → 得到 points_3d 什么是 points_3d&#xff1f; points_3d cv2.reprojectImageTo3D(disp, Q)points_3d.shap…

《深度剖析:SOAP与REST,API集成的两极选择》

API作为不同系统之间交互的桥梁&#xff0c;其设计与实现的优劣直接影响着整个软件生态的运转效率。而在API的设计领域&#xff0c;SOAP和REST犹如两座巍峨的山峰&#xff0c;各自代表着截然不同的设计理念与应用方向&#xff0c;成为开发者在构建API时必须慎重权衡的关键选项。…

非对称加密算法(RSA、ECC、SM2)——密码学基础

对称加密算法&#xff08;AES、ChaCha20和SM4&#xff09;Python实现——密码学基础(Python出现No module named “Crypto” 解决方案) 这篇的续篇&#xff0c;因此实践部分少些&#xff1b; 文章目录 一、非对称加密算法基础二、RSA算法2.1 RSA原理与数学基础2.2 RSA密钥长度…

Pillow 玩图术:轻松获取图片尺寸和颜色模式

前言 在这个“图像为王”的时代,谁还敢说自己没被一张图折磨过?一张图片不讲武德,说崩就崩,说卡就卡,仿佛像素里藏着程序员的眼泪。不管你是网页设计师、AI炼丹师,还是只是想把猫片修得像艺术品,图片的尺寸和颜色模式都是你必须掌握的第一手情报。如果你不知道它有多宽…

下载core5compat 模块时,被禁止,显示 - servese replied: Forbbidden. -->换镜像源

怎么解决&#xff1f; --->换镜像源 方法 1&#xff1a;使用命令行参数指定镜像源 在运行 Qt 安装器时&#xff0c;通过 --mirror 参数指定镜像源&#xff1a; # Windows qt-unified-windows-x64-online.exe --mirror https://mirrors.ustc.edu.cn/qtproject# Linux/macO…

WPF中Behaviors

行为的好处 可以把复杂的界面逻辑抽象出去&#xff0c;让xaml的界面设计更简单&#xff0c;更清爽 1.安装包 Microsoft.Xaml.Behaviors.Wpf2.简单实现拖动效果 <Border Width"100"Height"100"Background"Red"><i:Interaction.Behav…

GitHub 趋势日报 (2025年05月03日)

本日报由 TrendForge 系统生成 https://trendforge.devlive.org/ &#x1f4c8; 今日整体趋势 Top 10 排名项目名称项目描述今日获星总星数语言1hacksider/Deep-Live-Camreal time face swap and one-click video deepfake with only a single image⭐ 1582⭐ 59337Python2aip…

Oracle OCP认证考试考点详解083系列08

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 36. 第36题&#xff1a; 题目 解析及答案&#xff1a; 关于数据库闪回&#xff08;FLASHBACK DATABASE&#xff09;功能&#xff0c;以下…

优化01-统计信息

Oracle 的统计信息是数据库优化器生成高效执行计划的核心依据。它记录了数据库对象&#xff08;如表、索引、列等&#xff09;的元数据信息&#xff0c;帮助优化器评估查询成本并选择最优执行路径。以下是关于 Oracle 统计信息的详细介绍&#xff1a; 一、统计信息的分类 表统…

动态规划-面试题08.01三步问题-力扣(LeetCode)

一、题目解析 此题可以类比第N个泰波那契数 二、算法解析 1、状态表示 根据上面的分析和题目要求&#xff0c;dp[i]表示&#xff1a;到达i位置&#xff0c;一共有多少种方法 2、状态转移方程 以i位置的状态&#xff0c;以最近一步划分问题 dp[i] 从i-1->i dp[i-1] 从…

kotlin中枚举带参数和不带参数的区别

一 ✅ 代码对比总结 第一段&#xff08;带参数 工具方法&#xff09; enum class SeatPosition(val position: Int) {DRIVER_LEFT(0),DRIVER_RIGHT(1),SECOND_LEFT(2),SECOND_RIGHT(3);companion object {fun fromPosition(position: Int): SeatPosition? {return SeatPosi…

Java使用JDBC操作数据库

1.创建一个数据库一会用来连接 2.使用idea新建一个Java项目 3.在pom文件中加上相关依赖&#xff0c;并配置Maven路径 <dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>…

重名导致does not name a type

今天在Ubuntu24.04上编成时&#xff0c;makefile编译报错: falsecolor.h:48:9: error: ‘FalseColor’ does not name a type48 | FalseColor* content ;| ^~~~~~~~~~falsecolor.h的部分代码如下: class FalseColor {public:FalseColor(int w, int h){width …

Vue3 后台管理系统模板

Vue3 后台管理系统模板 gie仓库地址 一个基于 Vue3 TypeScript Element Plus 的后台管理系统模板&#xff0c;集成了动态路由和权限管理功能。 技术栈 Vue 3.2TypeScript 4.5Vue Router 4Vuex 4Element Plus 2.9AxiosLess 功能特性 &#x1f680; 基于 Vue3 最新技术栈开…

林业数智化转型初步设计方案

最近应林业方面的朋友要求,帮助其设计了林业方面的数字化智能化转型的方案设计,编写了如下内容,供大家参考,林业方面主要有三大方向,即林业生态、生物灾害和疫源疫病,目前已经建成了一些信息化系统,但在数字化智能化方面偏弱,就想着如何借助人工智能、物联网、大数据和…

springboot单体项目的执行流程

首先就是启动springboot项目&#xff0c;即执行主函数&#xff0c;这个主函数的类通常带有SpingBootApplication注解&#xff0c;类中的main方法就是程序的入口。 启动主函数后&#xff0c;SpringBoot会按特定顺序加载配置文件&#xff0c;如application.properties或applicat…

Python格式化字符串的四种方法

Python格式化字符串的四种方法 1.使用 % 运算符 %s 是一个字符串的占位符&#xff0c;而 “World” 是替换它的值 print("Hello, %s!" % "World") # 输出&#xff1a;Hello, World!你可以使用多个占位符 注意&#xff1a;多个变量占位&#xff0c;变量要…

【Redis】缓存|缓存的更新策略|内存淘汰策略|缓存预热、缓存穿透、缓存雪崩和缓存击穿

思维导图&#xff1a; Redis最主要的用途&#xff0c;三个方面&#xff1a; 1.存储数据&#xff08;内存数据库&#xff09; 2.缓存&#xff08;redis最常用的场景&#xff09; 3.消息队列 一、什么是缓存 我们知道对于硬件的访问速度来说&#xff0c;通常情况下&#xff1…

中阳视角下的趋势确认策略:以数据为核心的交易思维

中阳视角下的趋势确认策略&#xff1a;以数据为核心的交易思维 在动态交易市场中&#xff0c;如何在波动中捕捉相对确定的趋势&#xff0c;是每一位操作者关心的问题。“中阳”理念主张通过结构性价格分析&#xff0c;判断市场情绪的拐点。尤其是在出现大阳线或中阳线时&#x…