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 100
int 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个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符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; //1
cout << 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)
{
//先记录当前值返回,然后加1
MyInteger 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; //10
cout << *p2.m_a << endl; //10
cout << *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; //20
cout << 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; //10
cout << st.Sheep::m_A << endl; //10
cout << 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);
}
类型转换
dynamic_cast 动态转换
只能用于对象的指针或引用的转换,完成父类向子类的转换,父类中需要有虚函数,子类转父类是一定可以转换成功的,用普通的类型转换即可,不需要使用 dynamic_cast。
class Base
{
public:
virtual ~Base(){}
};
class Son : public Base{};
int main()
{
Base b1;
Son* s1 = dynamic_cast<Son*>(&b1); //类型不兼容
cout << (s1 == nullptr) << endl;
Base* b2 = new Base();
try
{
Son& s2 = dynamic_cast<Son&>(*b2); //类型不兼容
}
catch (const std::bad_cast& e)
{
cerr << "转换失败" << e.what() << endl;
}
Base* b3 = new Son();
Son* s3 = dynamic_cast<Son*>(b3); //成功转换
cout << s3<< endl;
return 0;
}
编译时检查:如果父类没有虚函数就会报错
运行时检查:转换时会检查 RTTI(Run Time Type Information)判断是否可以转换,b1 是 Base 类型,与目标类型不兼容,返回 nullptr,b2 同理,但引用转换失败会抛异常,b3 实际指向子类对象,类型匹配,可以转换成功。
static_cast
只要两个类型是兼容的就可以转换,所有的隐式转换都可以使用 static_cast 替代,只做编译时检查
class Base{};
class Son : public Base{};
class Other{};
int main()
{
Base* b1 = new Base();
Son* s1 = static_cast<Son*>(b1);
cout << s1 << endl;
Son* s2 = new Son();
Base* b2= static_cast<Base*>(s2);
cout << b2 << endl;
Other* o1 = new Other();
Son* s3 = static_cast<Son*>(o1); //编译错误
return 0;
}
子类转父类,或者父类转子类都行,不需要父类是多态类
reinterpret_cast 重解释转换
允许将一个类型的指针转换成另一个类型指针,而不会检查类型是否有效
int* i = new int(2);
float* f = reinterpret_cast<float*>(i);
const_cast 常量转换
主要用于常量转非常量,转换的类型必须是指针或者引用
const char* str = "hello";
char* str2 = const_cast<char*>(str);
str2[0] = 'w'; //内容不可修改,运行时报错
STL
六大组件:容器,算法,迭代器,仿函数,适配器,空间适配器
- 容器:各种数据结构,存储数据
序列容器:元素顺序排列,如 vector, list, deque
关联容器:通过键(key)快速查找元素
有序关联容器:基于红黑树实现,如 set, map
无序关联容器:基于哈希表实现,如 unordered_set, unordered_map。 - 算法:各种常用算法
- 迭代器:提供一种统一的方式遍历容器中的元素,容器和算法之间的胶合剂
- 仿函数:一个类将()重载为成员函数,行为类似函数
- 适配器:修改现有组件接口,使其适用于不同场景,如 stack(基于 deque 或 list 实现)
- 内存分配器:管理容器的内存分配和释放
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,不影响 capacity
v1.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
Lambda 表达式
Lambda 表达式用于快速定义一个匿名对象,也被称为闭包。
[捕获外部变量](参数列表) 可选限定符->返回类型
{
//函数代码
}
int x = 3;
float y = 2;
//x按值捕获,函数内部不能修改,y按引用捕获,函数内部可以修改
auto p = [x, &y](int a, int b)->float
{
y++;
return x * y + a * b;
};
cout << p(1, 2) << endl;
//加 mutable 限定符,捕获的变量都可以修改
auto p2 = [x, y](int a, int b) mutable->float
{
return x * y + a * b;
};
// [=] 表示默认按值捕获
auto p3 = [=](int a, int b)->float
{
return x * y + a * b;
};
// [&] 表示默认按引用捕获
auto p4 = [&](int a, int b)->float
{
return x * y + a * b;
};
// 添加特例
auto p5 = [=, &y](int a, int b)->float
{
return x * y + a * b;
};
//创建一个简单函数,作为另一个函数的参数
vector<int> nums{11,5,7,6,1,9,14,68,13,32};
sort(nums.begin(), nums.end(), [](int a, int b){return a < b; });
//泛型lambda
auto f = [](auto x, auto y)(return x + y;);