DAY 01
常量
宏常量: #define day 100
const修饰的变量: const int day = 100;
数据类型
整型、浮点型、科学计数法、字符型、bool
数据类型 | 占用空间 |
---|---|
short(短整型) | 2字节 |
int(整型) | 4字节 |
long(长整型) | Windows为4字节,Linux为4字节(32位),8字节(64位) |
long long(长长整型) | 8字节 |
float(单精度) | 4字节 |
double(双精度) | 8字节 |
char(字符型) | 1字节 |
bool(布尔类型) | 1字节 |
float赋值时,不加f默认为double类型。
如:float f1 = 3.14
float f2 = 3.14f
默认情况下,输出一个小数,最多会显示6位有效数字。
科学计数法:实型常量中e的前后必须均有数据,且其后必须为整数。
char(字符类型)变量不是把字符本身放在内存中存储,而是将对应的ASCII编码放到存储单元。只能用单引号赋值,并且里面只能有一个数值。
**bool(布尔类型)**除了0都是真。
转义字符
转义字符 | 含义 |
---|---|
\a | 警报 |
\b | 退格(BS),将当前位置移到前一列 |
\f | 换页(FF),将当前位置移到下页开头 |
\n | 换行(LF),将当前位置移到下一行开头 |
\r | 回车(CR),将当前位置移到本行开头 |
\t | 水平制表(HT),跳到下一个TAB位置 |
\v | 垂直制表(VT) |
\\ | 代表一个反斜线字符“\” |
\’ | 代表一个单引号(撇号)字符 |
\" | 代表一个双引号 |
\? | 代表一个问号 |
\0 | 数字0 |
\ddd | 8进制转义字符,d范围0–7 |
\xhh | 16进制转义字符,h范围0–9,A–F |
字符串类型
c风格字符串:char 变量名[] = “字符串值”;
c++风格字符串:string 变量名 = “字符串值”;
使用c++风格字符串时,必须声明头文件#include<string>
运算符的优先级
循环
switch
switch后面的表达式,可以是int、char和枚举型中的一种,不能是float
型变量, case后面必须是常量表达式,表达式中不能包含变量。
每一个case必须加break,以此跳出分支,否则就会继续运行下面分支。
每一个case中代码很长需要用{}将整个分支的代码段包起来,否则会出错。
do…while
do…while不管while后面条件是什么,都会先执行一次循环语句,再判断循环条件。
DAY 02
数组
一维数组定义:
1.数据类型 数组名[数组长度];
2.数据类型 数组名[数组长度] = {元素, 元素, 元素}; (不够长度的补0)
3.数据类型 数组名[] = {元素, 元素, 元素}; (自动分配长度)
二维数组定义:
1.数据类型 数组名[行数][列数];
2.数据类型 数组名[行数][列数] = {{元素, 元素}, {元素, 元素}};
3.数据类型 数组名[行数][列数] = {元素, 元素, 元素};
4.数据类型 数组名[][列数] = {元素, 元素, 元素};
数组名的用途:1.可以通过数组名查看数组首地址:cout << 数组名 << endl;
2.可以通过数组名查看数组占用的空间:cout << sizeof(数组名) << endl;
函数
函数的声明:提前告诉编译器函数的存在。可以声明多次。
函数的定义:定义只能有一次。
// 函数声明
int max(int a, int b);
// 函数定义
int max(int a, int b)
{
return a > b ? a : b;
}
函数分为值传递和地址传递。
值传递
int max(int a, int b)
{
return a > b ? a : b;
}
int main()
{
int a = 10;
int b = 20;
max(a, b);
}
地址传递
int max(int *a, int *b)
{
return *a > *b ? *a : *b;
}
int main()
{
int a = 10;
int b = 20;
max(&a, &b);
return 0;
}
地址传递可以修改形参的值
函数的分文件编写
1.创建.h后缀名的头文件
2.创建.cpp后缀名的源文件
3.在头文件中写函数的声明
4.在源文件中写函数的定义
5.源文件中包含头文件 #include “名字.h”
指针
指针是程序数据保存在内存中的地址,简单来说指针就是地址。
指针变量是用来保存这些地址的变量。
int a = 10;
// 指针定义语法,数据类型 * 指针变量名;
int *p;
// 让指针记录变量a的地址
p = &a;
// 通过解引用的方式来找到指针指向的内存
// 指针前加*代表解引用,找到指针指向的内存中的数据
cout << *p << endl;
// 也可以通过指针修改数据
*p = 10000;
cout << *p << endl;
指针所占空间大小:在32位系统下占用4个字节,64位系统下占用8个字节。
空指针:指针变量指向内存中编号为0的空间
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
// 空指针的初始化
int *p = NULL;
// 空指针是不可以进行访问的
// 执行修改空指针的语句,生成代码不会出现语法错误,但执行会崩溃:写入权限冲突
*p = 100;
// 0-255之间的内存编号是系统占用的,因此不可访问
野指针:指针变量指向非法的内存空间(你没有申请的空间)
// 野指针
int *p = (int *)0x1100;
// 野指针不能操作
// 生成代码不会出现语法错误,但执行会崩溃:读取访问权限冲突
cout << *p << endl;
const修饰指针
1.const修饰指针-----常量指针
特点:指针的指向可以修改,但是指针指向的值不可以修改。
int a = 10;
int b = 10;
const int *p = &a;
*p = 20; // 错误
p = &b; // 正确
2.const修饰常量-----指针常量
特点:指针的指向不可以修改,指针指向的值可以修改
int a = 10;
int b = 10;
int * const p = &a;
*p = 20; // 正确
p = &b; // 错误
3.const即修饰指针,又修饰常量
特点:指针的指向和指向的值都不可以修改
int a = 10;
int b = 10;
const int * const p = &a;
*p = 20; // 错误
p = &b; // 错误
结构体
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。
结构体的定义:struct 结构体名 { 结构体成员列表 };
结构体创建变量: 1.struct 结构体名 变量名
2.struct 结构体名 变量名 = {成员1值, 成员2值}
3.定义结构体时顺便创建变量
// 定义结构体时struct不可省略
struct Student
{
int id;
string name;
int age;
double score;
}s3;
// 结构体创建变量时struct可以省略
struct Student s1;
struct Student s2 = {1, "lisi", 18, 90};
定义结构体时struct不可省略
结构体创建变量时struct可以省略
结构体变量利用操作符"."访问成员
结构体指针
结构体指针通过"->"访问结构体属性
struct Student
{
int id;
string name;
int age;
double score;
};
int main()
{
struct Student stu;
struct Student *p = &stu;
p->age = 100;
return 0;
}
结构体嵌套
struct Student
{
int id;
string name;
int age;
double score;
};
struct Teacher
{
int id;
string name;
int age;
struct Student stu;
};
int main()
{
struct Teacher t;
t.stu.name = "李四";
return 0;
}
结构体中const使用场景
使用const防止修改结构体数据,防止误操作
struct Student
{
int id;
string name;
int age;
double score;
};
void printStudent(const struct Student *stu)
{
// stu.age = 100; // 不可以修改
cout << stu-> id;
}
int main()
{
struct Student s = {1, "lisi", 18, 100};
printStudent(&s);
return 0;
}
DAY 03
随机数
数组的函数传递
数组作为形参传递的是地址,是地址传递
void example(int b[], int c[])
{
cout << b[0] << end;
cout << c[0] << end;
}
int main()
{
int a[10] = {0, 1, 2, 3, 4, 5};
// 数组首地址传递
example(a, a+4);
return 0;
}
系统函数
system("pause"); // 请按任意键继续
system("cls"); // 清屏操作
C++内存分配
代码区:存放函数体的二进制代码,由操作系统进行管理
全局区:存放全局变量和静态变量以及常量
栈区:由编译器自动分配和释放,存放函数的参数值,局部变量等
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
存放cpu执行的机器指令
代码区是共享的,目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,防止程序修改它的指令
全局区:
全局变量和静态变量存放在此
还包括了常量区,字符串常量(如:“abcdef”)和其他常量(const修饰的全局变量)存放在此
该区域的数据在程序结束后由操作系统释放
程序运行后
栈区:
由编译器自动分配和释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,如果返回了地址,在vs中会保留一次。
int *func()
{
int a = 10;
return &a;
}
int main()
{
int *p = func();
cout << *p << endl; // 第一次可以打印正确数据,是因为编译器做了保留
cout << *p << endl; // 第二次这个数据就不再保留了,是乱码
return 0;
}
堆区:
由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
int *func()
{
// 利用new关键字可以将数据开辟到堆区
// 指针本身也是局部变量,放在栈上,指针保存的数据是放在堆区
int *p= new int(10); // new 数据类型(初始值)
return p;
}
int main()
{
int *p = func();
cout << *p << endl; // 正确
cout << *p << endl; // 正确
return 0;
}
new操作符
C++中主要利用new操作符在堆区开辟内存
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
利用new创建的数据,会返回该数据对应的指针
int main()
{
// new一个整型的空间,初始化值为10,返回一个指针
int *p = new int(10);
// 释放空间
delete p;
// new一个整型的长度为10的空间,返回数组的头地址
int *arr = new int[10];
// 释放数组空间
delete[] arr;
return 0;
}
注意:1、不要用void *接受new出的对象,原因是不能释放(不能调用析构函数)
2、在堆区创建自定义类型数组时,类中必须要存在默认构造函数,否则无法创建
void *p = new Person;
delete p; // 不能释放
delete (Person *)p; // 可以释放
DAY 04
引用
引用:给变量起别名
基本语法:数据类型 &别名 = 原名
本质:指针常量
注意事项:引用必须要初始化,必须指明是谁的别名
引用一旦初始化后,就不可以再更改引用了
对数组的引用
int arr[10];
int (&p)[10] = arr;
for (int i = 0; i < 10; i++)
{
arr[i] = i;
}
for (int i = 0; i < 10; i++)
{
cout << p[i] << endl;
}
// 先定义数组的类型,再定义引用
typedef int(ARRAY_TYPE)[10];
ARRAY_TYPE &p2 = arr;
for (int i = 0; i < 10; i++)
{
cout << p2[i] << endl;
}
引用做函数参数
引用传递形参也会修改实参
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
swap(a, b);
cout << a << " " << b << endl;
return 0;
}
引用用作函数的返回值
注意事项:1. 不要返回局部变量的引用,和返回局部变量的地址情况一样
2. 函数的调用可以作为左值
int& test()
{
static int a = 10;
return a;
}
int main()
{
int& ref = test();
cout << “ref = ” << ref << endl;
test() = 1000; // 函数调用作为左值
cout << “ref = ” << ref << endl; // ref的值被修改为1000
return 0 ;
}
常量引用
常量引用,修饰形参,是用来防止误操作
引用必须引用一块合法的内存空间
void showValue(const int &a)
{
a += 10; // 错误,不能修改
}
int main()
{
int a = 100;
showValue(a);
int & ref = 10 // 错误
// 加上const后是允许的,并且只能是只读的,编译器将代码修改为int temp = 10; const int & ref = temp;
const int & ref = 10;
return 0;
}
指针的引用
struct Person
{
int a;
};
// 使用二级指针
void test01(Person **p)
{
*p = (Person *)malloc(sizeof(Person));
}
// 使用指针的引用,同级的地位
void test02(Person* &p)
{
p = (Person *)malloc(sizeof(Person));
}
int main()
{
Person *p = NULL;
test01(&p);
test02(p);
return 0;
}
函数的提高
函数的默认值
注意事项:1.如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认参数
2.如果函数声明有默认参数,函数实现就不能有默认参数。声明和实现只能有一个默认参数。
int func(int a, int b, int c = 10)
{
return a + b + c;
}
int main()
{
cout << func(10, 20) << endl;
return 0;
}
函数的占位参数
C++中函数的形参列表里可以有占位参数,用来占位,调用函数时必须填补该位置
占位参数也可以有默认参数,可以不用填补
void func(int a, int)
{
cout << “调用func” << endl;
}
void func2(int a, int = 10)
{
cout << “调用func2” << endl;
}
int main()
{
func(10, 20);
func2(10);
return 0;
}
函数重载
函数名相同,提高复用性
条件:1、同一个作用域下
2、函数名称相同
3、函数参数类型不同,或者个数不同或者顺序不同
注意事项:1、重载碰上引用
2、重载碰上默认参数
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); // 出现二义性,出错
return 0;
}
类和对象
C++面向对象的三大特性:封装,继承,多态
C++认为万物皆为对象,对象上有其属性和行为
封装
将属性(成员属性或成员变量)和行为(成员函数或成员方法)作为一个整体,将属性和行为加以权限控制
语法:class 类名{访问权限:属性 / 行为};
访问权限:公共权限 public 类内可以访问,类外可以访问
保护权限 protected 类内可以访问,类外不可以访问 子可以访问父保护内容
私有权限 private 类内可以访问,类外不可以访问 子不可访问父私有内容
class 和struct区别:class默认权限私有,struct默认权限公共
成员属性设置为私有:1、可以自己控制读写权限
2、对于写可以检测数据的有效性
对象的初始化和清理
构造函数和析构函数
对象的初始化和清理
这两个函数将会被编译器自动调用,完成对象初始化和清理工作
对象的初始化和清理工作是编译器强制要求我们做的事情,因此如果我们不提供构造和析构,编译器会提供
编译器提供的构造函数和析构函数是空实现
构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序调用对象时候自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前加上符号~
3.析构函数不可以有参数,因此不可以发生重载
4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
DAY 05
构造函数的分类以及调用
两种分类划分:1、按照参数分类:无参构造(默认构造)和有参构造
2、按照类型分类:普通构造和拷贝构造
调用:1、括号法
2、显示法
3、隐式转换法
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;
}
int age;
};
int main()
{
// 1、括号法
Person p1; // 默认构造函数调用
Person p2(10); // 有参构造函数调用
Person p3(p2); // 拷贝构造函数
// 注意1:调用默认构造函数时候,不要加(),因为编译器会认为是函数的声明,不会认为是创建对象
// Person p2();
// 2、显示法
Person p4 = Person(10); // 有参构造
Person p5 = Person(p4); // 拷贝构造
// 注意2:Person(10)单独写就是匿名对象,当前行结束之后,马上析构
// 注意3:不能利用拷贝构造函数初始化匿名对象,编译器会认为是对象的声明,编译器会认为Person (p8) == Person p8
// Person (p8); // 对象的声明
// 3、隐式转换法
Person p6 = 10; // Person p5 = Person(10);
Person p7 = p6; // Person p6 = Person(p5);
return 0;
}
拷贝构造函数调用的时机
1、使用一个已经创建完毕的对象来初始化一个新对象
2、值传递的方式给函数参数传值
3、以值方式放回局部对象
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;
}
int age;
};
// 1.使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(10);
Person p2(p1);
}
// 2.值传递的方式给函数参数传值
void test02(Person p)
{
}
void dowork02()
{
Person p2;
test02(p2);
}
// 3.以值方式放回局部对象
Person test03()
{
Person p3;
return p3;
}
void dowork03()
{
Personp4 = test03();
}
int main()
{
// 1.
test01();
// 2.
dowork02();
// 3.
dowork03();
return 0;
}
拷贝构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1、默认构造函数(无参,函数体为空)
2、默认析构函数(无参,函数体为空)
3、默认拷贝构造函数,对属性进行拷贝
构造函数调用规则:
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造函数
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
如果类中的属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
初始化列表
类名():属性(初值), 属性(初值){}
class Person
{
// 默认构造,使得m_a = 10,m_b = 20
Person():m_a(10),m_b(20)
{
}
// 参数构造,使得m_a = a,m_b = b
Person(int a, int b):m_a(a),m_b(b)
{
}
int m_a;
int m_b;
};
类对象作为类成员
class A{}
class B
{
A a;
}
当其他类独享作为本类成员,构造时候先构造类对象,再构造自身。析构顺序相反。
静态成员
静态成员变量:
1.所有对象共享同一份数据
2.在编译阶段分配内存
3.类内声明,类外初始化
静态成员函数:
1.所有对象共享同一个函数
2.静态成员函数只能访问静态成员变量
静态成员函数访问方式:
1.通过对象访问
2.通过类名访问
静态成员函数也是有访问权限的
class Person
{
static void func()
{
m_a = 100; // 正确,静态成员函数可以修改访问静态成员变量
m_b = 200; // 错误,静态成员函数不可以访问非静态成员变量,无法区分到底是哪个对象的m_b
cout << "static void func()" << endl;
}
static int m_a; // 静态成员变量
int m_b; // 非静态成员变量
};
int Person::m_a = 500;
int main()
{
// 1、通过对象访问静态成员函数
Person p1;
p1.func();
// 2、通过类名访问静态成员函数
Person::func();
}
DAY 06
继承中关于子类构造函数的写法
构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。
如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。
构造原则如下:
1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。
2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。
3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。
4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。
6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式
class Base
{
public:
Base(int a){}
};
class Son:public Base
{
// 使用初始化列表,解决不能调用父类的默认构造函数
// 显示调用父类的哪个构造函数
Son(int a):Base(a){}
}
int main()
{
return 0;
}
DAY 07
C++对象模型和this指针
成员变量和成员函数分开存储
在C++中,类内成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
静态成员变量、静态成员函数和非静态成员函数不属于类的对象上,非静态成员函数只有一份
class Person
{
};
class Student
{
int m_A; // 非静态成员对象
};
class Teacher
{
int m_A; // 非静态成员变量
static int m_B; // 静态成员变量
void func(){} // 非静态成员函数
static void func2(){} // 静态成员函数
};
int Teacher::m_B = 0; // 类外初始化静态变量
void test01()
{
Person p;
// 空对象占用内存空间为:1
// C++编译器会给每个空对象也分配一个字节空间,
// 是为了区分空对象占用内存的位置
// 每个空对象也应该有一个独一无二的内存地址
cout << "sizeof p = " << sizeof(p) << endl;
}
void test02()
{
Student s;
// 含有非静态成员变量的对象占用内存空间为:4
// 非静态成员变量属于类的对象
cout << "sizeof s = " << sizeof(s) << endl;
}
void test03()
{
Teacher t;
// 含有非静态成员变量和静态成员变量的对象占用内存空间为:4
// 非静态成员变量属于类的对象,静态成员变量不属于类的对象
// 非静态成员函数不属于类的对象
// 非静态成员函数本质只有一份,里面通过方式区别谁在调用
// 静态成员函数不属于类的对象
cout << "sizeof t = " << sizeof(t) << endl;
}
int main()
{
test01();
test02();
test03();
return 0;
}
this指针
this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
1、当形参和成员变量同名时,可用this指针区分
2、在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
Person(int age)
{
// this指针指向被调用的成员函数所属的对象
this->age = age;
}
Person& PersonAddAge(Person &p)
{
this->age += age;
// this是指向对象的指针,而*this指向的就是对象的本体
return *this;
}
Person PersonAdd(Person &p)
{
this->age += age;
// this是指向对象的指针,而*this指向的就是对象的本体
return *this;
}
private:
int age;
};
int main()
{
Person p(20);
cout << "p的年龄为:" << p.age << endl;
Person p1(10);
Person p2(10);
// 链式编程思想
// 使用返回对象本身就可以实现连续调用
// p2的值为40
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
Person p3(10);
Person p4(10);
// 注意
// 当函数不是返回的引用时,表示拷贝构造函数,
// 返回一个新的对象
p4.PersonAdd(p3).PersonAdd(p3).PersonAdd(p3);
return 0;
}
空指针访问成员函数
C++中空指针也是可以访问成员函数的,但是要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
class Person
{
void showClassName()
{
cout << "this is Person Class" << endl;
}
void showAge()
{
// 默认属性前为this->m_age,而声明了空指针,无具体对象
// this指向也为空,所以会出错
// 可以加上以下代码,保证代码健壮性
if (this == NULL)
{
return;
}
cout << "age = " << m_age << endl;
}
int m_age;
};
int main()
{
Person *p = NULL;
p->showClassName(); // 正常输出
p->showAge(); // 出错,提示空指针
return 0;
}
const修饰成员函数
常函数:
1、成员函数后加const后称为常函数
2、常函数内不可以修改成员属性
3、成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
1、声明对象前加const称该对象为常对象
2、常对象只能调用常函数
class Person
{
// this指针的本质是指针常量,指针的指向不能变
// 类似于this == Person * const this
// 常函数
// 而函数后加const后,this == const Person * const this
// 在成员函数后加const,修饰this指向,让指针指向的值不可修改
void show() const
{
m_age = 100; // 出错,不可修改
m_b = 100; // 可以修改
}
void func()
{
m_age = 100;
}
int m_age;
mutable int m_b; // 特殊变脸,在常函数中也可修改
};
int main()
{
// 常对象
const Person p;
p.m_age = 100; // 出错,不可修改
p.m_b = 100; // 修改成功
p.func(); // 出错,不能调用普通成员函数
p.show(); // 正确,可以调用常函数
return 0;
}
友元
目的:让一个函数或者类访问另一个类中的私有成员
关键字:friend
注意:
1、友元关系不能继承
2、友元关系是单向的,A是B的朋友,B不一定是A的朋友
3、友元关系不具有传递性,A是B的朋友,B是C的朋友,A不一定是C的朋友
全局函数做友元
class Building
{
// goodGay全局函数是Building好朋友,可以访问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;
}
类做友元
class Building;
class GoodGay
{
public:
GoodGay();
void visit(); // 函数访问Building中的属性
Building *building;
};
class Building
{
// GoodGay是本类的好朋友,可以访问本类的私有成员
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; // 访问私有成员
}
int main()
{
GoodGay g;
g.visit();
return 0;
}
成员函数做友元
class Building;
class GoodGay
{
public:
GoodGay();
void visit(); // 函数访问Building中的私有属性
void visit2(); // 函数不可以访问Building中的私有属性
Building *building;
};
class Building
{
// GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
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_SittingRoom << endl;
cout << building ->m_BedRoom << endl;
}
void GoodGay::visit2()
{
cout << building ->m_SittingRoom << endl;
}
int mian()
{
GoodGay g;
g.visit();
return 0;
}
DAY 08
运算符重载
运算符重载也可以发生函数重载
加号运算符重载
class Person
{
public:
// 成员函数作为运算符重载
// 本质:Person p = p1.operator+(p1)
Person operator+(Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
return temp;
}
int m_A;
};
// 全局函数作为运算符重载
// 本质:Person p = operator+(p1, p2)
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
return temp;
}
int main()
{
Person p1;
p1.m_A = 10;
Person p2;
p2.m_B = 20;
Person p = p1 + p2;
cout << p.m_A << endl;
return 0;
}
左移运算符重载
作用:可以输出自定义数据类型
一般不会用成员函数重载左移运算符,因为无法实现cout在左侧
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;
};
ostream& operator<<(ostream &out, Person &p)
{
out << p.m_A;
out << p.m_B;
return out;
}
int main()
{
Person p(10, 20);
cout << p << endl;
return 0;
}
递增运算符重载
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger(int a)
{
m_A = a;
}
// 重载前置++运算符,返回引用是为了一直对第一个数据进行操作
MyInteger& operator++()
{
m_A++;
return *this;
}
// 重载后置++运算符,int占位符,区分前置和后置递增
// 后置递增返回值,不能返回引用,因为是局部变量的引用
MyInteger operator++(int)
{
MyInteger temp = *this;
m_A++;
return temp;
}
private:
int m_A;
};
ostream& operator<<(ostream& cout, MyInteger myint)
{
cout << myint.m_A;
return cout;
}
int main()
{
MyInteger myint(10);
cout << myint++ << endl;
cout << myint << endl;
cout << ++(++myint) << endl;
cout << myint << endl;
return 0;
}
赋值运算符重载
C++编译器至少给一个类添加4个函数
1、默认构造函数(无参,函数体为空)
2、默认析构函数(无参,函数体为空)
3、默认拷贝构造函数,对属性值进行值拷贝
4、赋值运算符operator=,对属性值进行值拷贝,浅拷贝
如果类中属性指向堆区,做赋值操作时也会出现深浅拷贝的问题
class Person
{
public:
Person(int a)
{
m_A = new int(a);
}
~Person()
{
if (m_A != NULL)
{
delete m_A;
m_A = NULL;
}
}
// 返回引用是为了实现连等操作
Person& operator=(Person& p)
{
if (m_A != NULL)
{
delete m_A;
m_A = NULL;
}
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;
cout << *p2.m_A << endl;
cout << *p3.m_A << endl;
return 0;
}
关系运算符重载
==、!=、>=、<=
class Person
{
public:
Person(string name, int age);
bool operator==(Person &p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}
string m_Name;
int m_Age;
};
Person::Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
int main()
{
Person p1("张三", 20);
Person p2("张三", 20);
if (p1 == p2)
{
cout << "p1与p2相等" << endl;
}
else
{
cout << "p1与p2不相等" << endl;
}
return 0;
}
函数调用运算符重载
()
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
class MyPrint
{
public:
void operator()(int a)
{
cout << a << endl;
}
};
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
int main()
{
MyPrint myprint;
myprint(10);
MyAdd myadd;
int result = myadd(10, 20);
cout << result << endl;
// 匿名函数对象
cout << MyAdd()(100, 100) << endl;
return 0;
}
继承
减少重复代码
继承的基本语法
class 子类:继承方式 父类,如:
class A:public B
子类:派生类
父类:基类
继承方式
公共继承 public
保护继承 protected
私有继承 private
继承中的对象模型
父类中所有非静态成员属性都会被子类继承下去
父类中私有成员属性是被编译器隐藏了,访问不到,但确实被继承去了,例如:
class Parent
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son:public Parent
{
public:
int m_D;
};
int main()
{
Son s;
// 16
cout << "sizeof(s) = " << sizeof(s) << endl;
return 0;
}
使用命令行查看类的属性和大小
1、打开vs的开发者命令行
2、进入程序目录
3、输入
cl /d1 reportSingleClassLayout类名 程序文件
4、可以看到类的属性图,哪些是继承的,哪些是自身的
继承中的构造和析构顺序
构造顺序:父类先,子类后
析构顺序:子类先,父类后
class Parent
{
public:
Parent()
{
cout << "父类构造完成" << endl;
}
~Parent()
{
cout << "父类析构完成" << endl;
}
};
class Son :public Parent
{
public:
Son()
{
cout << "子类构造完成" << endl;
}
~Son()
{
cout << "子类析构完成" << endl;
}
};
int main()
{
Son s;
cout << "------------------" << endl;
return 0;
}
继承同名成员处理方式
访问子类同名成员,直接访问
访问父类同名成员,需要加作用域
如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,如果想要访问父类中被隐藏的同名成员函数,需要加作用域
class Parent
{
public:
void show()
{
cout << "Parent::show()" << endl;
}
void show(int a)
{
cout << "Parent::show(" << a << ")" << endl;
}
int m_A = 100;
};
class Son :public Parent
{
public:
void show()
{
cout << "Son::show()" << endl;
}
int m_A = 200;
};
int main()
{
Son s;
// 访问子类的成员变量
cout << s.m_A << endl;
// 访问父类的成员变量
cout << s.Parent::m_A << endl;
// 访问子类的成员函数
s.show();
// 访问父类的成员函数
s.Parent::show();
s.Parent::show(10);
return 0;
}
总结:
1、子类对象可以直接访问到子类中同名成员
2、子类对象加作用域可以访问到父类同名成员
3、当子类和父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
继承同名静态成员处理方式
静态成员与非静态成员出现同名,处理方式一致
访问子类同名成员,直接访问
访问父类同名成员,需要加作用域
静态成员有两种访问方式,通过类对象访问,通过类名访问
主要体现在使用类名访问
class Parent
{
public:
void show()
{
cout << "Parent::show()" << endl;
}
static int m_A;
};
int Parent::m_A = 100;
class Son :public Parent
{
public:
void show()
{
cout << "Son::show()" << endl;
}
static int m_A;
};
int Son::m_A = 200;
int main()
{
// 通过类访问
cout << Son::m_A << endl;
// 第一个::表示通过类名访问,第二个::表示访问父类作用域下
cout << Son::Parent::m_A << endl;
cout << Parent::m_A << endl;
return 0;
}
多继承语法
class 子类:继承方式 父类1, 继承方式 父类2...
不建议使用,多个父类中含有同名的成员,即出现二义性,需要加作用域访问
class ParentA
{
public:
int m_A = 100;
};
class ParentB
{
public:
int m_A = 200;
};
class Son :public ParentA, public ParentB
{
public:
int m_B = 300;
};
int main()
{
Son s;
// 访问ParentA中的属性
cout << s.ParentA::m_A << endl;
// 访问ParentB中的属性
cout << s.ParentB::m_A << endl;
return 0;
}
菱形继承(钻石继承)
概念:
两个派生类继承同一个基类
又有某个类同时继承两个派生类
问题:
二义性
继承多份基类数据,但只需继承一份即可,造成资源浪费
class Base
{
public:
int m_A = 100;
};
class ParentA: public Base{};
class ParentB: public Base{};
class Son :public ParentA, public ParentB{};
int main()
{
Son s;
// 不能直接访问m_A
// 访问ParentA中的属性
cout << s.ParentA::m_A << endl;
// 访问ParentB中的属性
cout << s.ParentB::m_A << endl;
return 0;
}
解决方法:利用虚继承,在继承前加上关键字virtual
加上后,最大的类叫做虚基类
数据只有一份,通过不同作用域访问的数据相同
class Base
{
public:
int m_A = 100;
};
class ParentA: virtual public Base{};
class ParentB: virtual public Base{};
class Son :public ParentA, public ParentB{};
int main()
{
Son s;
// 访问ParentA中的属性
cout << s.ParentA::m_A << endl;
// 访问ParentB中的属性
cout << s.ParentB::m_A << endl;
cout << s.m_A << endl;
return 0;
}
下图可以看出数据只有一份,vbptr叫做虚基类指针,指向虚基类表,虚基类表中有数据,是偏移量,当前指针地址加上偏移量找到数据
下图中,两个指针都指向同一个数据
Son并不是继承数据,而是继承了指针,通过指针找到数据
DAY 09
多态
静态动态:函数重载和运算符重载
动态多态:派生类和虚函数
静态多态和动态多态区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
class Animal
{
public:
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);
return 0;
}
#include<iostream>
using namespace std;
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);
return 0;
}
多态的基本概念
实现多态条件:继承关系
子类重写父类虚函数(父类加virtual,子类可加可不加)
使用动态多态:父类的引用或指针指向子类的对象
声明虚函数后,vfptr,虚函数(表)指针,指向虚函数表,表内记录虚函数地址
子类继承父类后,也会继承父类的指针和虚函数表,当子类进行重写后,虚函数表内部会替换为子类的虚函数地址,注意,父类的虚函数表不变,当父类的指针或引用指向子类对象时,发生多态。
例如,当父类Animal中不声明虚函数时
当父类Animal中声明虚函数时
当子类Cat中不进行重写时
当子类Cat中进行重写时
多态好处:
1、组织结构清晰
2、可读性强
3、对于前期和后期扩展和维护性高
多态的深度剖析
虚函数无参时
#include<iostream>
using namespace std;
class Animal
{
public:
// 虚函数
virtual void speak()
{
cout << "动物说话" << endl;
}
virtual void eat()
{
cout << "动物在吃饭" << endl;
}
};
class Cat : public Animal
{
public:
void speak()
{
cout << "小猫说话" << endl;
}
virtual void eat()
{
cout << "小猫在吃饭" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗说话" << endl;
}
virtual void eat()
{
cout << "小狗在吃饭" << endl;
}
};
void test01()
{
Animal* animal = new Cat;
//(int *)animal,拿到首地址转为int*
//*(int *)animal,解引用,找到虚函数表,跳到虚函数表中
//(int *)*(int *)animal,告诉步长四字节,相当于指向索引0
//*(int *)*(int *)animal,speak地址
//(void (*)()) (*(int*)*(int*)animal),函数指针指向地址
//((void (*)()) (*(int*)*(int*)animal))(),函数调用
((void (*)()) (*(int*)*(int*)animal))(); // 调用小猫说话
((void (*)()) (*((int*)*(int*)animal + 1)))(); // 调用小猫在吃饭
}
int main()
{
test01();
return 0;
}
虚函数有参时
#include<iostream>
using namespace std;
class Animal
{
public:
// 虚函数
virtual void speak(int a)
{
cout << "动物说话" << endl;
}
virtual void eat()
{
cout << "动物在吃饭" << endl;
}
};
class Cat : public Animal
{
public:
void speak(int a)
{
cout << "小猫说话" << endl;
}
virtual void eat()
{
cout << "小猫在吃饭" << endl;
}
};
class Dog :public Animal
{
public:
void speak(int a)
{
cout << "小狗说话" << endl;
}
virtual void eat()
{
cout << "小狗在吃饭" << endl;
}
};
void test01()
{
Animal* animal = new Cat;
// 有参调用,虽然调用成功,但会出错引出异常
// C++默认调用惯例__stdcall
// 而下面写法调用惯例__cdecl
// ((void (*)(int )) (*(int*)*(int*)animal))(5); // 调用小猫说话
// typedef void (*FUNC)(int); // 定义函数指针类型
//((void (*)(int )) (*(int*)*(int*)animal))(5);等于(FUNC (*((int*)*(int*)animal)))(5);
// 仍会出错
// 需要指定惯例为__stdcall,不会出现错误异常
typedef void (_stdcall *FUNC)(int);
(FUNC(*((int*)*(int*)animal)))(5);
}
int main()
{
test01();
return 0;
}
纯虚函数和抽象类
语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也成为抽象类
抽象类的特点:
1、无法实例化对象
2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构的共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构的区别:纯虚析构,该类属于抽象类,无法实例化对象
父类指针在析构时,不会调用子类的析构函数,导致子类如果有堆区属性,出现内存泄露,例
class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}
virtual void speak() = 0;
~Animal()
{
cout << "Animal析构函数调用" << endl;
}
};
class Cat : public Animal
{
public:
Cat(int a)
{
cout << "Cat构造函数调用" << endl;
m_A = new int(a);
}
void speak()
{
cout << "小猫说话" << endl;
}
~Cat()
{
cout << "Cat析构函数调用" << endl;
if (m_A != NULL)
{
delete m_A;
m_A = NULL;
}
}
int* m_A;
};
int main()
{
Animal *a = new Cat(10);
delete a;
return 0;
}
子类的析构函数未被调用,出现内存泄露
解决方法:将父类的析构改为虚析构,例
class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}
virtual void speak() = 0;
virtual ~Animal()
{
cout << "Animal析构函数调用" << endl;
}
};
class Cat : public Animal
{
public:
Cat(int a)
{
cout << "Cat构造函数调用" << endl;
m_A = new int(a);
}
void speak()
{
cout << "小猫说话" << endl;
}
~Cat()
{
cout << "Cat析构函数调用" << endl;
if (m_A != NULL)
{
delete m_A;
m_A = NULL;
}
}
int* m_A;
};
int main()
{
Animal *a = new Cat(10);
delete a;
return 0;
}
纯虚析构需要声明也需要实现,例
class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}
virtual void speak() = 0;
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal析构函数调用" << endl;
}
class Cat : public Animal
{
public:
Cat(int a)
{
cout << "Cat构造函数调用" << endl;
m_A = new int(a);
}
void speak()
{
cout << "小猫说话" << endl;
}
~Cat()
{
cout << "Cat析构函数调用" << endl;
if (m_A != NULL)
{
delete m_A;
m_A = NULL;
}
}
int* m_A;
};
int main()
{
Animal *a = new Cat(10);
delete a;
return 0;
}
总结:
1、虚析构和纯虚析构就是用来解决通过父类指针释放子类对象
2、如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3、拥有纯虚析构函数的类也属于抽象类
多态案例:
class CPU
{
public:
virtual void caculate() = 0;
};
class VideoCard
{
public:
virtual void display() = 0;
};
class Memory
{
public:
virtual void storage() = 0;
};
class IntelCpu :public CPU
{
void caculate()
{
cout << "Intel的CPU" << endl;
}
};
class IntelVideoCard :public VideoCard
{
void display()
{
cout << "Intel的显卡" << endl;
}
};
class InteleMemory :public Memory
{
void storage()
{
cout << "Intel的内卡" << endl;
}
};
class LenoveCpu :public CPU
{
void caculate()
{
cout << "Lenove的CPU" << endl;
}
};
class LenoveVideoCard :public VideoCard
{
void display()
{
cout << "Lenove的显卡" << endl;
}
};
class LenoveMemory :public Memory
{
void storage()
{
cout << "Lenove的内卡" << endl;
}
};
class Computer
{
public:
Computer(CPU * cpu, VideoCard * vc, Memory *men)
{
m_cpu = cpu;
m_vc = vc;
m_men = men;
}
void work()
{
m_cpu->caculate();
m_vc->display();
m_men->storage();
}
~Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
if (m_men != NULL)
{
delete m_men;
m_men = NULL;
}
}
private:
CPU * m_cpu;
VideoCard* m_vc;
Memory* m_men;
};
int main()
{
Computer *c = new Computer(new IntelCpu, new IntelVideoCard, new LenoveMemory);
c->work();
delete c;
return 0;
}
文件操作
C++中对文件操作需要包含头文件<fstream>
操作文件的三大类:
1、ofstream:写操作
2、ifstream:读操作
3、fstream:读写操作
文本文件
以文本的ASCII码形式存储
文件的打开方式:
打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
注意:文件打开方式可以配合使用,利用"|"操作符
写文件
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
// 1、包含头文件<fstream>
// 2、创建流对象
ofstream ofs;
// 3、打开文件
ofs.open("test.txt", ios::out);
// 4、写文件
ofs << "正在写文件" << endl;
ofs << "写完成" << endl;
// 5、关闭文件
ofs.close();
return 0;
}
总结:
1、文件操作必须包含头文件fstream
2、读文件可以利用ofstream,或者fstream
3、打开文件时候需要指定操作文件的路径以及打开方式
4、利用<<可以向文件写数据
5、操作完毕,要关闭文件
读文件
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
int main()
{
// 1、包含头文件<fstream>
// 2、创建流对象
ifstream ifs;
// 3、打开文件
ifs.open("test.txt", ios::in);
// 判断是否打开成功
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}
// 4、读文件,四种读取方式
// 一、
char buf[1024] = { 0 };
while (ifs >> buf)
{
cout << buf << endl;
}
// 二、每行读取
char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}
// 三、全局函数每行读取
string buf;
while (getline(ifs, buf))
{
cout << buf << endl;
}
// 四、读取单个字符
char c;
while ( (c = ifs.get()) != EOF)
{
cout << c;
}
// 5、关闭文件
ifs.close();
return 0;
}
总结:
1、读文件可以使用ifstream,或者fstream类
2、利用is_open函数可以判断文件是否打开成功
3、close关闭文件
二进制文件
以文本的二进制形式存储
打开方式指定为ios::binary
写文件
不仅可以操作内置类型,也可以操作自定义类型
不要使用string,会出现问题
使用write函数写数据
#include<iostream>
#include<fstream>
using namespace std;
struct Person
{
char name[100];
int age;
};
int main()
{
// 1、包含头文件<fstream>
// 2、创建流对象
ofstream ofs("test.txt", ios::out | ios::binary);
// 3、打开文件
//ofs.open("test.txt", ios::in | ios::binary);
// 4、写文件
Person p = { "张三", 18 };
ofs.write((const char*)&p, sizeof(Person));
// 5、关闭文件
ofs.close();
return 0;
}
读文件
使用read函数写数据
#include<iostream>
#include<fstream>
using namespace std;
struct Person
{
char name[100];
int age;
};
int main()
{
// 1、包含头文件<fstream>
// 2、创建流对象
ifstream ifs;
// 3、打开文件
ifs.open("test.txt", ios::in | ios::binary);
// 判断文件是否打开成功
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}
// 4、写文件
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << p.name << p.age << endl;
// 5、关闭文件
ifs.close();
return 0;
}