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;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;
}
字符串操作
#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;
}
指针
#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; //错误,不能更改
引用做函数参数,和地址传递效果一样,语法更简单
//值传递,形参不会修饰实参
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 之后变成只读
}
类和对象
C++ 面向对象三大特征:封装,继承,多态
封装:把属性和行为作为一个整体,通过 public,protect,private 对属性和行为加以权限控制。
struct 和 class 的区别
唯一区别就是默认的访问权限不一样,struct 默认权限为 public,class 默认权限为 private
构造函数,拷贝构造,析构函数
默认情况下,编译器至少给一个类添加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;}
}
类模板
类模板与函数模板区别主要有两点:
- 类模板必须显示指定类型
- 类模板在模板参数列表中可以有默认参数
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 文件,链接不到模板函数的具体实现
解决办法:
- 直接引用源文件 .cpp,如 #include “person.cpp”,一般不这样做
- 将 .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);
}
相关文章:
C++ 复习
VS 修改 C 语言标准 右键项目-属性 输入输出 //引用头文件,用<>包裹起来的一般是系统提供的写好的代码 编译器会在专门的系统路径中去进行查找 #include <iostream> //自己写的代码文件一般都用""包裹起来 编译器会在当前文件所在的目录中査…...
数字智慧方案5877丨智慧交通项目方案(122页PPT)(文末有下载方式)
篇幅所限,本文只提供部分资料内容,完整资料请看下面链接 https://download.csdn.net/download/2301_78256053/89575494 资料解读:智慧交通项目方案 详细资料请看本解读文章的最后内容。 智慧交通项目方案是一个全面的设计框架,…...
如何封装一个线程安全、可复用的 HBase 查询模板
目录 一、前言:原生 HBase 查询的痛点 (一)连接管理混乱,容易造成资源泄露 (二)查询逻辑重复,缺乏统一的模板 (三)多线程/高并发下的线程安全性隐患 (四…...
VLM Qwen2.5VL GRPO训练微调 EasyR1 多机多卡训练(2)
在之前博客进行了简单的训练尝试:https://www.dong-blog.fun/post/2060 在本博客,将会深入进行多机多卡训练,以及调整训练奖励函数。 之前构建了镜像: docker build . -t kevinchina/deeplearning:r1 FROM hiyouga/verl:ngc-th2.6.0-cu126-vllm0.8.4-flashinfer0.2.2-cx…...
基于建造者模式的信号量与理解建造者模式
信号量是什么? AI解释:信号量(Semaphore)是操作系统中用于 进程同步与互斥 的经典工具,由荷兰计算机科学家 Edsger Dijkstra 在 1965 年提出。它本质上是一个 非负整数变量,通过原子操作(P 操作…...
笔试专题(十四)
文章目录 mari和shiny题解代码 体操队形题解代码 二叉树中的最大路径和题解代码 mari和shiny 题目链接 题解 1. 可以用多状态的线性dp 2. 细节处理:使用long long 存储个数 3. 空间优化:只需要考虑等于’s’,‘sh’,shy’的情况…...
2025年五一数学建模A题【支路车流量推测】原创论文讲解
大家好呀,从发布赛题一直到现在,总算完成了2025年五一数学建模A题【支路车流量推测】完整的成品论文。 给大家看一下目录吧: 摘 要: 一、问题重述 二.问题分析 2.1问题一 2.2问题二 2.3问题三 2.4问题四 2.5 …...
Linux系统:进程程序替换以及相关exec接口
本节重点 理解进程替换的相关概念与原理掌握相关程序替换接口程序替换与进程创建的区别程序替换的注意事项 一、概念与原理 进程程序替换是操作系统中实现多任务和资源复用的关键机制,允许进程在运行时动态加载并执行新程序。 1.1 定义 进程程序替换是指用新程…...
STM32复盘总结——芯片简介
1、stm32介绍 STM32是ST公司基于ARM Cortex-M内核开发的32位微控制器 STM32常应用在嵌入式领域,如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等 STM32功能强大、性能优异、片上资源丰富、功耗低,是一款经典的嵌入式微控制器 目…...
安装深度环境anaconda+cuda+cudnn+pycharm+qt+MVS
下载anaconda,链接:link 默认电脑有显卡驱动,没有的话直接进NVIDIA官网:https://www.nvidia.cn/geforce/drivers/ 下载。 下载cuda 链接:https://developer.nvidia.com/cuda-toolkit-archive 下载cudnn安装包,链接:https://developer.nvidia.com/rdp/cudnn-archive 备注:…...
泰迪杯特等奖案例学习资料:基于多模态特征融合的图像文本检索系统设计
(第十二届泰迪杯数据挖掘挑战赛B题特等奖案例解析) 一、案例背景与核心挑战 1.1 应用场景与行业痛点 随着智能终端与社交媒体的普及,图像与文本数据呈现爆炸式增长,跨模态检索需求日益迫切。传统方法面临以下问题: 语义鸿沟:图像与文本的异构特征分布差异显著,导致跨模…...
进程与线程:05 内核级线程实现
内核级线程代码实现概述 这节课我们要讲内核级线程到底是怎么做出来的,实际上就是要深入探讨内核级线程的代码实现。 在前两节课中,我们学习了用户级线程和内核级线程是如何进行切换的,以及实现切换的核心要点。那两节课讲述的内容…...
Laravel 12 实现 API 登录令牌认证
Laravel 12 实现 API 登录令牌认证 在 Laravel 12 中实现基于令牌(Token)的 API 认证,可以使用 Laravel Sanctum 或 Laravel Passport。以下是两种方式的实现方法: 方法一:使用 Laravel Sanctum (轻量级 API 认证) 1. 安装 Sanctum compo…...
【Git】万字详解 Git 的原理与使用(上)
🥰🥰🥰来都来了,不妨点个关注叭! 👉博客主页:欢迎各位大佬!👈 文章目录 1. 初识 Git1.1 Git 是什么?1.2 为什么要有 Git 2. 安装 Git2.1 Linux-Ubuntu 安装 Git2.2 Windo…...
Python高级爬虫之JS逆向+安卓逆向1.7节: 面向对象
目录 引言: 1.7.1 先理解面向过程 1.7.2 再理解面向对象 1.7.3 面向对象的三大特征 1.7.4 类属性,类方法,静态方法 1.7.5 构造函数,对象属性,对象方法 1.7.6 爬虫接单实现了雪糕自由 引言: 大神薯条老师的高级爬虫+安卓逆向教程: 这套爬虫教程会系统讲解爬虫的初…...
SpringBoot基础(原理、项目搭建、yaml)
SpringBoot:javaweb的一个框架,基于Spring开发,SpringBoot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷的开发新一代基于Spring框架的应用程序,它与Spring框架紧密结合用于提升Spring开发者体验的…...
MTV-SCA:基于多试向量的正弦余弦算法
3 正弦余弦算法 (SCA) 正弦余弦算法(SCA)是为全局优化而开发的,并受到两个函数,正弦和余弦的启发。与其他基于启发式种群的算法一样,SCA在问题的预设最小值和最大值边界内随机生成候选解。然后,通过应用方…...
STL之vector容器
vector的介绍 1.vector是可变大小数组的容器 2.像数组一样,采用连续的空间存储,也就意味着可以通过下标去访问,但它的大小可以动态改变 3.每次的插入都要开空间吗?开空间就要意味着先开临时空间,然后在拷贝旧的到新…...
Android学习总结之jetpack组件间的联系
在传统安卓开发中,UI 组件(Activity/Fragment)常面临三个核心问题: 生命周期混乱:手动管理 UI 与数据的绑定 / 解绑,易导致内存泄漏(如 Activity 销毁后回调仍在触发)。数据断层&am…...
linux的信号量初识
Linux下的信号量(Semaphore)深度解析 在多线程或多进程并发编程的领域中,确保对共享资源的安全访问和协调不同执行单元的同步至关重要。信号量(Semaphore)作为经典的同步原语之一,在 Linux 系统中扮演着核心角色。本文将深入探讨…...
【安装指南】Centos7 在 Docker 上安装 RabbitMQ4.0.x
目录 前置知识:RabbitMQ 的介绍 一、单机安装 RabbitMQ 4.0.7版本 1.1 在线拉取镜像 二、延迟插件的安装 2.1 安装延迟插件 步骤一:下载延迟插件 步骤二:将延迟插件放到插件目录 步骤三:启动延迟插件 步骤四:重启 RabbitMQ 服务 步骤五:验收成果 步骤六:手动…...
Android和iOS测试的区别有哪些?
作为移动端测试工程师,Android 和 iOS 的测试差异直接影响测试策略设计。本文从测试环境、工具链、兼容性、发布流程等维度全面解析,并附实战建议。 1. 测试环境差异 维度AndroidiOS设备碎片化高(厂商/分辨率/系统版本多样)低(仅苹果设备,版本集中)系统开放性开放(可Ro…...
spring中的@PostConstruct注解详解
基本概念 PostConstruct 是 Java EE 规范的一部分,后来也被纳入到 Spring 框架中。它是一个标记注解,用于指示一个方法应该在依赖注入完成后被自动调用。 主要特点 生命周期回调:PostConstruct 标记的方法会在对象初始化完成、依赖注入完成…...
大模型开发学习笔记
文章目录 大模型基础大模型的使用大模型训练的阶段大模型的特点及分类大模型的工作流程分词化(tokenization)与词表映射 大模型的应用 进阶agent的组成和概念planning规划子任务分解ReAct框架 memory记忆Tools工具\工具集的使用langchain认知框架ReAct框架plan-and-Execute计划…...
【android Framework 探究】pixel 5 内核编译
相关文章: 【android Framework 探究】android 13 aosp编译全记录 【android Framework 探究】android 13 aosp 全记录 - 烧录 一,环境 主机 -> Ubuntu 18.04.6 LTS 内存 -> 16GB 手机 -> pixel 5 代号redfin。kernel代号redbull 二…...
PowerBI实现点击空白处隐藏弹窗(详细教程)
PowerBI点击空白处隐藏弹窗 第五届PowerBI可视化大赛中亚军作品:金融企业智慧经营分析看板 有个功能挺好玩的:点击空白处隐藏弹窗,gif动图如下: 我们以一个案例分享下实现步骤: 第一步, 先添加一个显示按钮ÿ…...
【git】获取特定分支和所有分支
1 特定分支 1.1 克隆指定分支(默认只下载该分支) git clone -b <分支名> --single-branch <仓库URL> 示例(克隆 某一个 分支): git clone -b xxxxxx --single-branch xxxxxxx -b :指定分支…...
Windows配置grpc
Windows配置grpc 方法一1. 使用git下载grph下载速度慢可以使用国内镜像1.1 更新子模块 2. 使用Cmake进行编译2.1 GUI编译2.2 命令行直接编译 3. 使用Visual Studio 生成解决方法 方法二1. 安装 vcpkg3.配置vckg的环境变量2. 使用 vcpkg 安装 gRPC3. 安装 Protobuf4. 配置 CMake…...
【学习笔记】深入理解Java虚拟机学习笔记——第2章 Java内存区域与内存溢出异常
第2章 Java内存区域与内存溢出异常 2.1 概述 略 2.2 运行时数据区域 2.2.1 程序计数器 线程私有,记录执行的字节码位置 2.2.2 Java 虚拟机栈 线程私有,存储一个一个的栈帧,通过栈帧的出入栈来控制方法执行。 -栈帧:对应一个…...
数字智慧方案6189丨智慧应急综合解决方案(46页PPT)(文末有下载方式)
资料解读:智慧应急综合解决方案 详细资料请看本解读文章的最后内容。 在当前社会环境下,应急管理的重要性愈发凸显。国务院发布的《“十四五” 国家应急体系规划》以及 “十四五” 智慧应急专项规划,明确了应急管理体系建设的方向和重点&…...
解决 3D Gaussian Splatting 中 SIBR 可视化组件报错 uv_mesh.vert 缺失问题【2025最新版!】
一、📌 引言 在使用 3D Gaussian Splatting(3DGS)进行三维重建和可视化的过程,SIBR_gaussianViewer_app 是一款官方推荐的本地可视化工具,允许我们在 GPU 上实时浏览重建结果。然而,许多用户在启动该工具时…...
见多识广4:Buffer与Cache,神经网络加速器的Buffer
目录 前言传统意义上的Buffer与Cache一言以蔽之定义与主要功能BufferCache 数据存储策略二者对比 神经网络加速器的bufferInput BufferWeight BufferOutput Buffer与传统buffer的核心区别总结 前言 知识主要由Qwen和Kimi提供,我主要做笔记。 参考文献: …...
微服务中组件扫描(ComponentScan)的工作原理
微服务中组件扫描(ComponentScan)的工作原理 你的问题涉及到Spring框架中ComponentScan的工作原理以及Maven依赖管理的影响。我来解释为什么能够扫描到common模块的bean而扫描不到其他模块的bean。 根本原因 关键在于**类路径(Classpath)**的包含情况: Maven依赖…...
C++之类和对象基础
⾯向对象三⼤特性:封装、继承、多态 类和对象 一.类的定义1. 类的定义格式2.类域 二.实例化1.对象2.对象的大小 三.this指针 在 C 的世界里,类和对象构成了面向对象编程(Object-Oriented Programming,OOP)的核心框架&…...
【DIY小记】新手小白超频遇到黑屏问题解决分享
最近玩FPS游戏的时候,发现以前一顿操作超频之后的电脑,有一定概率会出问题。具体表现比如一种是,电脑显示器直接黑屏,所有键盘交互没有响应,只能直接重启电脑,还有一种是偶现卡顿,直接死机或者卡…...
虚幻引擎 IK Retargeter 编辑器界面解析
我来为您详细解释这段关于虚幻引擎IK Retargeter编辑器界面的文本,它描述了动画重定向系统的核心组件和工作原理。 Retarget Phases (重定向阶段) 这部分介绍了动画重定向过程中的三个关键计算阶段,每个阶段都可以单独启用或禁用,这对于调试…...
uc系统中常用命令、标准C库函数和系统调用
目录 一、常用命令 env echo $name 键值 export name unset name gcc -c xxx.c ar 命令 ar -r libxxx.a xxx1.o xxx2.o gcc -c -fpic xxx.c gcc -shared -fpic xxx1.c xxx2.c -o libxxx.so kill [-信号] PID kill -l 软链接:ln -s xxx yyy 硬链接&…...
OpenHarmony - 驱动使用指南,HDF驱动开发流程
OpenHarmony - HDF驱动开发流程 概述 HDF(Hardware Driver Foundation)驱动框架,为驱动开发者提供驱动框架能力,包括驱动加载、驱动服务管理、驱动消息机制和配置管理。并以组件化驱动模型作为核心设计思路,让驱动开发…...
C++负载均衡远程调用学习之UDP SERVER功能
目录 1.LARSV0.9-配置功能 2.LARSV0.10-upd-server的实现 3.LARSV0.10-udp-client的实现 1.LARSV0.9-配置功能 2.LARSV0.10-upd-server的实现 3.LARSV0.10-udp-client的实现...
word交叉引用图片、表格——只引用编号的处理方法
交叉引用图片/表格 在“引用”选项卡上的“题注”组中,单击“插入题注”。勾选【从题注中排除标签】。在文中插入题注。 【注 意】 这时候插入的题注只有编号项了。然后手动打上标签【TABLE】,并在标签和编号项之间加上【样式分隔符,AltCt…...
平台介绍-开放API接口-鉴权
平台的理念是一个组织内部只建一套系统。但是现实情况是,组织内部已经建立了很多系统,是不能一次性替代的,只能先搭起平台,然后逐步开始替换。这样就不可避免的存在其他系统和平台进行交互的问题。 平台为此设计了开放API接口。其…...
【Bootstrap V4系列】 学习入门教程之 组件-警告框(Alert)
Bootstrap V4 学习入门教程之 组件-警告框(Alert) 警告框(Alert)一、示例二、链接的颜色三、添加其它内容四、关闭警告框 通过 JavaScript 触发行为触发器本组件所暴露的事件 警告框(Alert) 通过精炼且灵活…...
【服务器通信-socket】——int socket(int domain, int type, int protocol);
#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); domain: AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址 AF_INET6 与上面类似,不过是来用IPv6的地…...
洛谷P1014(Cantor 表[NOIP 1999 普及组])题解
题目大意:求Cantor表(按照Z字形排列(如第一项是1/1,然后是1/2,2/1,3/1,2/2))的第N项。 那么,我们需要找出Cantor表的排列规律。根据题目中的Z字形描述&#x…...
【愚公系列】《Manus极简入门》012-自我认知顾问:“内在探索向导”
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! …...
密码学_加密
目录 密码学 01 密码基础进制与计量 02 加解密基操 替换 移位 编码 编码 置换 移位 加解密强度 03 对称加密算法(私钥) 工作过程 缺陷 对称加密算法列举? DES DES算法架构 DES分组加密公式 DES中ECB-CBC两种加密方式 3DES 由于DES密钥太短…...
w317汽车维修预约服务系统设计与实现
🙊作者简介:多年一线开发工作经验,原创团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹赠送计算机毕业设计600个选题excel文…...
云盘系统设计
需求背景 网盘面向大量C端用户 1000w用户 DAU 20% 每天10次 QPS: 1000w * 0.2 * 10 / 100k 500 峰值估计:500 * 5 2500 功能需求 支持上传,下载,多端共同在线编辑,数据冲突处理 非功能需求 1.latency 20s左右 2.可用性与…...
西电雨课堂《知识产权法》课后作业答案
目录 第 1 章 1.1 课后作业 1.2 课后作业 第 2 章 2.1 课后作业 2.2 课后作业 2.3 课后作业 2.4 课后作业 2.5 课后作业 2.6 课后作业 2.7 课后作业 2.8 课后作业 2.9 课后作业 2.10 课后作业 第 3 章 3.1 课后作业 3.2 课后作业 3.3 课后作业 3…...
通信协议记录仪-产品规格书
以下是为 通信协议记录仪(ProtoLogger Pro) 的详细产品规格书,覆盖 技术细节、场景需求、竞品差异化,确保可作为产品开发、市场营销及竞品分析的核心依据。 通信协议记录仪产品规格书 产品名称:ProtoLogger Pro(中文名称:蹲守…...