C++基础学习笔记(上)
文章目录
一、常量
1.宏常量:通常在文件上方,使用#define 常量名 常量值 定义;
2.const修饰的变量:const 数据类型 常量名 = 常量值,不可修改。
二、数据类型:
1.整型:
short:短整型,2B,-32768~32767
int:整型,4B,-231~231-1
long:长整型,win 4B,Unix 4B(32b),8B(64b)
long long:8B
2.sizeof()关键字可以统计数据类型占的内存大小。
3.实型(浮点型):
float:4B
double:8B
科学计数法:f = 3e-2
4.字符型:char 1B,字符型变量将字符对应的ASCII码放到存储单元。
5.字符串型:
char 变量名[] = ”xx“ ——C风格
string 变量名 = ”xx“ ——C++风格
6.bool型:1B
7.数据输入:cin >> 变量
8.前置递增: 先让变量递增,再进行表达式运算。后置递增: 先进行表达式运算,再让变量递增。
三、程序流程结构
1.三目运算符返回的是变量,可以继续赋值。
2.do-while循环:do{循环语句} while{循环条件},与while的区别在于先执性一次循环语句,再判断循环条件。
do {
cout << num << endl;
num ++;
}
while(num < 10);
水仙花数:
do {
int i = num % 10;
int j = num / 10 % 10;
int k = num / 100;
if (i*i*i + j*j*j + k*k*k == num){
cout << num << endl;
}
num ++;
}
while(num < 1000);
3.for(起始表达式;条件表达式;末尾循环体){循环语句;} 等价于下:
int i = 0;
for (;;) {
if(i > 10){break;}
cout << i <<endl;
i ++;
}
continue本次循环剩下的代码不执行。
4.goto标记:如果标记的名称存在,执行到goto语句时会跳转到标记的位置。
goto FLAG;
FLAG:
cout << "goto!!" << endl;
四、数组
1.数组放在一块连续的内存空间中,每个元素都是相同的数据类型。创建方式:
int arr[5];
int arr[] = {1, 2, 3, 4, 5};
int arr[];
2.冒泡排序:
for (int i = 0; i < size - 1; ++i) {//总轮数为元素个数-1
for (int j = 0; j < size - i - 1 ; ++j) {//对比次数 = 元素个数 - 当前轮数 - 1
if (arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
五、函数
1.函数的声明:函数写在main函数之后会找不到函数,需要事先声明。声明函数只有函数类型、函数名和参数列表。声明可以写多次,但函数定义只能有一次。
2.函数的分文件编写:
(1)创建.h头文件;(2)创建.cpp源文件;(3)在头文件中写函数的声明;(4)在源文件中写函数的定义,包含头文件。
六、指针(int *类型)
1.指针变量保存一个地址。通过解引用的方式使用指针:*p
2.指针在32b系统下占4B,在64b系统占8B;
3.空指针:指针变量指向内存中编号为0的空间,一般用于初始化指针变量:int *p = NULL;空指针指向的内存不允许访问,0-255的内存编号是系统使用。
4.野指针:指针变量指向非法内存空间。空指针和野指针都市自己申请的空间,不允许访问。
5.const修饰指针(3种情况):
const修饰指针:常量指针:const int * p = &a;指针的指向可以修改,但指针指向的值不能修改。
const修饰常量:指针常量:int * const p = &a;指针的指向不可以修改,但指针指向的值可以修改。
const既修饰指针又修饰常量:const int * const p = &a;指针的指向和指向的值都不能修改。
6.利用指针访问数组中的元素:
int arr[] = {1,2,3,4,5,6,7,8,9};
int * p = arr;//指向数组首地址
for (int i = 0; i < 9; ++i) {
cout << *p << endl;
p++;//指针向后偏移4B
}
7.指针和函数:在函数中需要修改传入的实参,则需要传入地址。
8.指针、数组、函数实现冒泡排序:
void bubbleSort(int * arr, int len){
cout << "arr:" << arr <<endl;//arr地址
cout << "* arr:" << * arr <<endl;//数组首元素
for (int i = 0; i < len - 1; ++i) {
for (int j = 0; j < len - i - 1; ++j) {
if (arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
void printArr(int * arr, int len){
for (int i = 0; i < len; ++i) {
cout << arr[i] << endl;
}
}
int main(){
int arr[] = {12,2,5,4,1,6,17,28,9,3};
int len = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, len);
printArr(arr, len);
}
七、结构体
1.结构体是用户自定义的数据类型,允许存储不同的数据类型。语法:struct 结构体名 { 结构体成员列表 };通过结构体创建变量的方式有三种(常用一二种),创建时struct关键字可胜省略,定义时不可省略:
struct 结构体名 变量名
struct 结构体名 变量名 = {成员1值, 成员2值…}
定义结构体时顺便创建变量
struct Student{
string name;
int age;
int score;
} s3;//第三种
int main(){
//第一种
struct Student s1;
s1.name = "a";
s1.age = 13;
s1.score = 100;
//第二种
struct Student s2 = {"b", 18, 99};
//第三种
s3.name = "c";
s3.age = 22;
s3.score = 66;
}
2.结构体数组:将自定义的结构体放入数组中方便维护。语法:struct 结构体名 数组名[元素个数] = { {}, {}, {}, …}
struct Student{
string name;
int age;
int score;
};
int main(){
Student stuArr[3] = {
{"a", 16, 100},
{"b", 18,99},
{"c",22,66}
};
stuArr[0].name = "d";//修改结构体变量
for (int i = 0; i < 3; ++i) {//遍历输出结构体变量
cout << stuArr[i].name << stuArr[i].age << stuArr[i].score << endl;
}
}
3.结构体指针:作用:通过指针访问结构体中的成员。利用操作符 -> 可以通过结构体指针访问结构体属性。
struct Student{
string name;
int age;
int score;
};
int main(){
Student s = {"a", 16, 100};
Student * p = &s;//通过指针指向结构体变量
cout << "姓名:" << p->name << endl;
}
4.结构体嵌套结构体:例:一个老师带多个学生。
struct Student{
string name;
int age;
int score;
};
struct Teacher{
int id;
string name;
int age;
struct Student stu;
};
int main(){
Teacher t;
t.id = 1;
t.name = "王老师";
t.age = 45;
t.stu.name = "小明";
t.stu.age = 20;
t.stu.score = 100;
cout << t.name << t.id << t.age << t.stu.name << t.stu.age << endl;
}
5.结构体做函数参数:作用:将结构体作为参数向函数中传递,传递方式有值传递和地址传递。
struct Student{
string name;
int age;
int score;
};
void printStu(Student * s){
cout << s->name << s->age << s->score << endl;
}
int main(){
Student s;
s.name = "小明";
s.age = 18;
s.score = 100;
printStu(&s);
}
6.结构体重const使用:用来防止误操作。
//将函数的形参改为指针可以减少占用内存空间,而且不会复制新的副本出来。
//但会有误修改结构体数据的风险。使用const可以避免,有修改的操作会报错
void printStu(const Student * s){
cout << s->name << s->age << s->score <<endl;
}
int main(){
Student s = {"小明", 18, 100};
printStu(&s);
}
7.例:每个老师带五个学生
struct Student{
string sName;
int score;
};
struct Teacher{
string tName;
Student sArr[5];
};
void allocateSpace(Teacher tArr[], int len){
string nameSeed = "ABCDE";
for (int i = 0; i < len; ++i) {
tArr[i].tName = "teacher_";
tArr[i].tName += nameSeed[i];
for (int j = 0; j < 5; ++j) {
tArr[i].sArr[j].sName = "student_";
tArr[i].sArr[j].sName += nameSeed[j];
tArr[i].sArr[j].score = 66;
}
}
}
void printInfo(Teacher tArr[], int len){
for (int i = 0; i < len; ++i) {
cout << "老师姓名:\t" << tArr[i].tName << endl;
for (int j = 0; j < 5; ++j) {
cout << "学生姓名:\t" << tArr[i].sArr[j].sName << "学生分数:\t" << tArr[i].sArr[j].score << endl;
}
}
}
int main(){
//创建三个老师的数组
Teacher tArr[3];
//给三老师和其学生赋值
int len = sizeof(tArr) / sizeof(tArr[0]);
allocateSpace(tArr, len);
//输出老师和学生信息
printInfo(tArr, len);
}
8.例:冒泡排序输出按结构体的某个值排序结果:
struct Hero{
string name;
int age;
string sex;
};
void bubbleSort(Hero heroArr[], int len){
for (int i = 0; i < len - 1; ++i) {
for (int j = 0; j < len - i - 1; ++j) {
if (heroArr[j].age > heroArr[j+1].age){
Hero temp = heroArr[j];
heroArr[j] = heroArr[j+1];
heroArr[j+1] = temp;
}
}
}
}
void printArr(Hero heroArr[],int len){
for (int i = 0; i < len; ++i) {
cout << "名字:" << heroArr[i].name <<"年龄:" << heroArr[i].age << "性别:" << heroArr[i].sex << endl;
}
}
int main(){
Hero heroArr[5] = {
{"a", 23, "男"},
{"b", 25,"男"},
{"c", 44, "男"},
{"d", 53, "女"},
{"e", 12, "男"}
};
bubbleSort(heroArr, 5);
printArr(heroArr, 5);
}
9.通讯录管理系统示例:
#include <iostream>
#include <string>
# define MAX 1000
using namespace std;
struct Person{
string m_Name;
int m_Sex;
int m_Age;
string m_Phone;
string m_Addr;
};
struct AddrBooks{//通讯录结构体
Person personArr[MAX];
int m_Size;//当前记录的联系人个数
};
//显示菜单界面
void showMenu(){
cout << " 输入要执行的功能数字!" << endl;
cout << "***** 1.添加联系人 *****" << endl;
cout << "***** 2.显示联系人 *****" << endl;
cout << "***** 3.删除联系人 *****" << endl;
cout << "***** 4.查找联系人 *****" << endl;
cout << "***** 5.修改联系人 *****" << endl;
cout << "***** 6.清空联系人 *****" << endl;
cout << "***** 0.退出 *****" << endl;
}
void addPerson(AddrBooks * addrBooks){
if (addrBooks->m_Size == MAX){
cout << "通讯录已满!无法加入!" << endl;
return;
}
else{
string name;
cout << "请输入名字:" << endl;
cin >> name;
addrBooks->personArr[addrBooks->m_Size].m_Name = name;
int sex;
cout << "请输入性别:(1男,2女)" << endl;
while (true){
cin >> sex;
if (sex == 1 || sex == 2) {
addrBooks->personArr[addrBooks->m_Size].m_Sex = sex;
break;
}
cout << "输入有误!重新输入!" << endl;
}
int age = 0;
cout << "请输入年龄:" << endl;
cin >> age;
addrBooks->personArr[addrBooks->m_Size].m_Age = age;
string phone;
cout << "请输入电话:" << endl;
cin >> phone;
addrBooks->personArr[addrBooks->m_Size].m_Phone = phone;
string addr;
cout << "请输入地址:" << endl;
cin >> addr;
addrBooks->personArr[addrBooks->m_Size].m_Addr = addr;
cout << "添加成功!" << endl;
addrBooks->m_Size ++;
}
}
void showPerson(AddrBooks * addrBooks){
if (addrBooks->m_Size == 0){
cout << "当前记录为空!" << endl;
} else{
cout << "姓名\t\t性别\t\t年龄\t\t电话\t\t\t地址\t\t\t" << endl;
for (int i = 0; i < addrBooks->m_Size; ++i) {
cout <<addrBooks->personArr[i].m_Name << "\t\t" << (addrBooks->personArr[i].m_Sex == 1?"男":"女")
<< "\t\t" << addrBooks->personArr[i].m_Age << "\t\t" << addrBooks->personArr[i].m_Phone
<< "\t\t\t" << addrBooks->personArr[i].m_Addr << "\t\t\t" << endl;
}
}
}
int isExist(AddrBooks * addrBooks, string name){
for (int i = 0; i < addrBooks->m_Size; i++) {
if (addrBooks->personArr[i].m_Name == name){
return i;
}
}
return -1;
}
void deletePerson(AddrBooks * addrBooks){
cout << "输入要删除的联系人:" << endl;
string name;
cin >> name;
int ret = isExist(addrBooks, name);
if (ret != -1){
for (int i = ret; i < addrBooks->m_Size; ++i) {
addrBooks->personArr[i] = addrBooks->personArr[i+1];
addrBooks-> m_Size --;//更新数量
cout << "删除成功!" << endl;
}
}else{
cout << "查无此人!" << endl;
}
}
void findPerson(AddrBooks * addrBooks){
cout << "输入要查找的联系人:" << endl;
string name;
cin >> name;
int ret = isExist(addrBooks, name);
if (ret != -1){
cout<< "姓名:" << addrBooks->personArr[ret].m_Name << "\n性别:"<< addrBooks->personArr[ret].m_Sex
<< "\n年龄:" << addrBooks->personArr[ret].m_Age << "\n电话:" << addrBooks->personArr[ret].m_Phone
<< "\n住址:" << addrBooks->personArr[ret].m_Addr << endl;
} else{
cout << "查无此人!" << endl;
}
}
void modifyPerson(AddrBooks * addrBooks){
cout << "输入要修改的联系人:" << endl;
string name;
cin >> name;
int ret = isExist(addrBooks, name);
if (ret != -1){
string name;
cout << "请输入姓名:" << endl;
cin >> name;
addrBooks->personArr[ret].m_Name = name;
int sex;
cout << "请输入性别:" << endl;
while (true) {
cin >> sex;
if (sex == 1 || sex == 2) {
addrBooks->personArr[ret].m_Sex = sex;
break;
} else {
cout << "输入有误,重新输入!" << endl;
}
}
int age;
cout << "请输入年龄:" << endl;
cin >> age;
addrBooks->personArr[ret].m_Age = age;
int phone;
cout << "请输入电话:" << endl;
cin >> phone;
addrBooks->personArr[ret].m_Phone = phone;
string address;
cout << "请输入地址:" << endl;
cin >> address;
addrBooks->personArr[ret].m_Addr = address;
}
}
void cleanPerson(AddrBooks *addrBooks){
addrBooks->m_Size = 0;
cout << "清空成功!" << endl;
}
int main(){
AddrBooks addrBooks;
addrBooks.m_Size = 0;
int select = 0;
while (true) {
showMenu();
cin >> select;
switch (select) {
case 0://退出
cout << "欢迎下次使用!" << endl;
return 0;
case 1://添加联系人
addPerson(&addrBooks);
break;
case 2://显示
showPerson(&addrBooks);
break;
case 3://删除
deletePerson(&addrBooks);
break;
case 4://查找
findPerson(&addrBooks);
break;
case 5://修改
modifyPerson(&addrBooks);
break;
case 6://清空:将通讯录中数量置零做逻辑清空即可
cleanPerson(&addrBooks);
break;
}
}
}
八、内存模式——内存四区(代码区、全局区、栈区、堆区)
1.代码区存放函数体的二进制代码,由操作系统进行管理;全局区存放全局变量、静态变量、常量(字符串常量、const修饰的全局变量,但const修饰的局部变量不在全局区);栈区在函数执行完后由编译器自动分配释放,存放函数的形参和局部变量等;堆区由程序员分配和释放,若不释放则程序结束时由OS回收。
意义:不同的区域存放不同的数据赋予不同的生命周期,更灵活编程。
2.在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域——代码区和全局区。
**代码区:**存放cpu执行的机器指令。**特点:**代码区是共享的,目的是对于频繁执行的程序只需要在内存中有一份代码;代码区是只读的,防止程序意外的修改了它的指令。
**全局区:**在程序结束后由OS释放。const修饰的局部变量和局部变量都不在此区。
**栈区:**注意事项:不能返回局部变量的地址,栈区开辟的数据由编译器自动释放。
int * func(){
int a = 10;//局部变量,存放在栈区,栈区数据在函数执行完后释放
return &a;//返回局部变量的地址
}
int main(){
int *p = func();
cout << *p << endl;//输出10,因为编译器做了一次保留
cout << *p << endl;//输出32767,第二次数据不再保留
}
堆区:new关键字开辟内存,会返回该数据对应类型的指针;delete关键字释放内存。
int * func(){
int *p = new int(10);//利用new关键字开辟堆区数据,指针在栈区,开辟的空间在堆区。
int *arr = new int[10];
for(int i=0;i<10;i++){
arr[i] = i+1;
}
return p;
}
int main(){
int *p = func();
cout << *p << endl;
delete p;//delete关键字释放内存空间
delete[] arr;
}
九、引用
1.引用的作用:给变量起别名,不同于值传递,不会再复制一份数据。语法:数据类型 &别名 = 原名;创建引用时必须初始化(赋值),且初始化后不可再改变。别名和原名指向同一块内存,可以操作原内存。
2.**引用的本质:在C++内部实现是一个指针常量:*int& ref = a; 等价于int const ref = &a;所以引用不可更改。内部发现ref = 1;自动转换为*ref = 1;
3.函数传参是,可以利用引用让形参修饰实参,可以简化指针修改实参。
//引用方式传参,起了一个一样的别名
void swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
int main(){
int a = 1;
int b = 2;
swap(a, b);
cout << a << b << endl;
}
4.引用做函数的返回值:用法:如果函数的返回值是引用,函数调用可以作为左值,不可返回局部变量的引用,因为函数执行后栈区数据释放,返回的地址已经不是局部变量本来的地址。
int& func0(){//不要返回局部变量的引用
int a = 1;
return a;
}
int& func1(){
static int a = 1;//静态变量在全局区
return a;//返回a的引用,等价于返回a这个变量
}
int main(){
int &ref0 = func0();
cout << "ref0:" << ref0 << endl;//第一次结果正确,因为编译器做了保留(但我的结果是两次都不正确)
cout << "ref0:" << ref0 << endl;//第二次结果错误,因为内存已经释放
int &ref1 = func1();
cout << "ref1:" << ref1 << endl;//每次都能正确输出
cout << "ref1:" << ref1 << endl;
//如果函数的返回值是引用,则函数调用可作为左值
func1() = 100;//等价于 a = 100
cout << "ref1:" << ref1 << endl;
}
5.常量引用主要用来修饰形参,防止误操作。在函数形参列表中,加const修饰形参,防止形参改变实参。
//int& ref = 10;引用本身需要一个合法的内存空间,此句错误
const int& ref = 10;
//加入const后,变为int temp = 10;const int& ref = temp;
void printA(const int &val){
val = 101;//不小心使用赋值操作,改变了a的值,使用const则不能再修改
cout << "val=" << val << endl;
}
int main(){
int a = 100;
printA(a);
}
十、函数提高
1.函数缺省参数:形参可使用缺省参数。如果某个位置有了默认参数,则从这个位置往后的形参都必须要有缺省参数。
2.函数的声明有了默认参数,则函数的实现不能再有默认参数。声明和实现只能有一个有缺省参数。
3.函数占位参数:形参列表中可以有占位参数用来占位,调用函数时必须填补该位置。语法:返回值类型 函数名 (数据类型){}
4.函数重载:作用:函数名可以相同以提高复用性,根据函数中的参数不同调用不同的函数。
需满足:同一个作用域下;函数名相同;函数参数类型不同或个数不同或顺序不同(函数返回值不能作为重载条件)
注意事项:
(1)引用作为重载条件:形参是引用类型和const修饰的引用类型,使用函数重载时,如果实参是变量则调用前者,是常量则会调用后者。
(2)函数重载遇到函数有缺省参数:当函数有缺省参数时尽量不要使用重载。
十一、类和对象
1.封装:将属性和行为作为一个整体表现事物,将属性和行为加以权限控制。语法:class 类名{访问权限:属性/行为}
2.类中的属性和行为统称为成员,类中的属性称为成员属性或成员变量,行为称为成员函数或成员方法。
3.访问权限:
public:类内、类外都可访问
protected:类内可以访问,类外不可访问,继承时子可访问父内容。
private:类内可以访问,类外不可访问,继承时子不可访问父内容。
4.class和struct唯一区别:默认的访问权限不同:struct默认权限是公有,class默认权限是私有。
5.类中的成员是另一个类的对象,称该成员是对象成员。
//Person中包含Phone类
class Phone{
public:
string m_pName;
Phone(string p_name){
m_pName = p_name;
}
};
class Person{
public:
string m_name;
Phone m_phone;
//Phone m_phone = pName 隐式转换法创建对象
Person(string name, string pName):m_name(name), m_phone(pName){
}
};
void init(){
//先构造Phone类,再构造Person类
// 先执行Person类的析构函数,再执行Phone类的析构函数
Person p("wzh", "iphone");
cout << p.m_name << p.m_phone.m_pName << endl;
}
int main(){
init();
}
6.构造函数和析构函数:被编译器自动调用,完成对象的初始化和清理工作。若不提供构造函数和析构函数,编译器会自动提供空实现的构造函数和析构函数。
(1)构造函数:语法:类名(){ };没有返回值也不写void;名称与类名相同,可以有参数,因此可以发生重载;调用对象时自动调用,且只调用一次。
(2)析构函数:语法:~类名(){ };没有返回值也不写void;名称与类名相同,前加~,不能有参数,因此不会发生重载;销毁对象时自动调用,且只调用一次。
(3)构造函数的分类:按参数分为有参构造、无参构造;按类型分为普通构造、拷贝构造:Person(const Person &p)。
(4)构造函数的三种调用方式:
**<1>括号法:**调用默认构造函数时不加括号(),如果加了括号编译器认为是函数声明。
**<2>显式法:**Person p = Person(10);
调用拷贝构造:Person p = Person(p0); 单独的Person(10)是匿名对象,此行执行完后系统立即回收匿名对象。
不能利用拷贝函数初始化匿名对象:Person(p2)——编译器认为:Person p2,即实例化一个对象。
**<3>隐式转换法:**Person p = 10;等价于Person p = Person(10)。拷贝构造:Person p = p1;
7.拷贝构造函数的调用时机:
使用一个已经创建完毕的对象来初始化一个新对象;
值传递的方式给函数参数传值;
以值方式返回局部对象。
class Person{
public:
int m_age;
Person(){
cout << "person默认构造函数调用" << endl;
}
Person(int age){
m_age = age;
cout << "person有参构造函数调用" << endl;
}
Person(const Person &p) {
m_age = p.m_age;
cout << "person拷贝构造函数调用" << endl;
}
~Person(){
cout << "person析构函数调用" << endl;
}
};
void test(){
Person p0(20);
Person p1(p0);
cout << "p0的年龄:" << p0.m_age <<endl;
cout << "p1的年龄:" << p1.m_age <<endl;
}
int main(){
test();
}
/**输出:
person有参构造函数调用
person拷贝构造函数调用
p0的年龄:20
p1的年龄:20
person析构函数调用
person析构函数调用*/
8.构造函数调用规则:默认情况下,c++编译器至少给一个类添加三个函数:
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认构造拷贝函数,对属性进行值拷贝
调用规则:
用户定义有参构造函数,则不再提供默认无参构造,但会提供默认拷贝构造函数
用户定义拷贝构造函数,则不再提供其他构造函数
9.深拷贝和浅拷贝:
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
class Person{
public:
int m_age;
int * m_height;//身高(定义为指针,将数据开辟到堆区)
Person(){
cout << "默认构造函数" << endl;
}
Person(int age, int height){
m_age = age;
m_height = new int(height);//创建在堆区
cout << "有参构造函数" << endl;
}
~Person(){
//手动将堆区的数据释放
if (m_height != NULL){
delete m_height;
m_height =NULL;
}
cout << "默认析构函数" << endl;
}
//下面这是增加的深拷贝代码,自定义拷贝构造函数
Person(const Person &p){
cout << "拷贝构造函数调用" << endl;
m_age = p.m_age;
//m_height = p.m_height;编译器默认实现这行代码
//深拷贝:重新开辟空间,让自己的指针指向此空间
m_height = new int(* p.m_height);
}
};
void test01(){
Person p1(18, 170);
cout << "p1的age:" << p1.m_age <<"p1的height:" << * p1.m_height << endl;
Person p2(p1);
/**若利用编译器的拷贝构造函数,则会做浅拷贝操作,给p2定义 m_age 变量并赋值18,
* 定义 * m_height 变量并赋值为p1中 * m_height 的指针所在内存地址
* p2调用析构函数释放内存后,p1又调用再次释放,带来的问题是重复释放
* 应使用 深拷贝 进行,自定义拷贝构造函数,重新在堆区申请内存存放指针 m_height 的值*/
cout << "p2的age:" << p2.m_age <<"p2的height:" << * p2.m_height << endl;
}
int main(){
test01();
}
10.初始化列表:语法:构造函数():属性1(值1),属性2(值2)…{ }
class Person{
public:
string m_name;
int m_age;
Person(string name, int age):m_name(name), m_age(age){
}
};
int main(){
Person("wzh", 18);
}
11.静态成员:在成员变量和成员函数前加static。
静态成员变量:
所有对象共享同一份数据;
在编译阶段分配内存;
在类内声明,类外初始化。
静态成员函数:
所有对象共享同一个函数;
静态成员函数只能访问静态成员变量。
类外访问不到私有静态成员函数。
class Person{
public:
static void func() {
m_a = 100;
// m_b = 200; 静态成员函数不能访问非静态成员变量,无法区分是那个对象的变量
cout << "static" << endl;
}
static int m_a;
int m_b;
};
int Person::m_a = 0;
void tests(){
//通过对象访问
Person p;
p.func();
//所有对象共享一个函数,通过类名访问
Person::func();
}
int main(){
tests();
}
12.C++对象模型:
(1)成员变量和成员函数分开存储。只有非静态成员变量才属于类的对象上面。空对象占用1B的空间。在类内定义一个int数据则对象占用4B空间,再增加任意静态成员变量不增加对象占用的空间大小。
(2)this指针:每一个非静态成员函数只会存在一个函数实例,也就是多个同类型的对象共用一块代码,C++通过this指针指向被调用的成员函数所属的对象。this指针是隐含每一个非静态成员函数内的一种指针,直接使用即可。
用途:<1>当形参和成员变量同名时,可用this来区分;<2>在类的非静态成员函数中返回对象本身,可使用return *this。
class Person{
public:
Person(int age){
this->age = age;
}
// void PersonAddAge(Person &p){
Person & PersonAddAge(Person &p){
//这里如果不使用引用,而是返回值,则下面链式每次调用返回的是新建的一个Person创建的对象复制的一份数据
this->age += p.age;
return * this;
}
int age;
};
void test1(){
Person p1(18);
cout << p1.age << endl;
}
void test2(){
Person p1(5);
Person p2(10);
//链式编程,由于原PersonAddAge函数返回的是空,所以不能连续调用,需要改为返回Person的引用
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2年龄:" << p2.age << endl;
}
int main(){
test1();
test2();
}
(3)空指针可以调用成员函数,但需注意有没有用到this指针,若用到this指针则需要加以判断保证代码健壮性。
class Person{
public:
void printName(){
cout << "名字!" << endl;
}
void printAge() {
if(this == NULL){
return;
}
cout << m_age << endl;//这里默认是this->m_age
}
int m_age;
};
int main(){
Person * p = NULL;
p->printName();
p->printAge();//这里不对,空指针调用不到m_age
}
13.const修饰成员函数:
常函数:
成员函数后加const后称这个函数为常函数;
常函数内不能修改成员属性;
成员属性声明时加关键字mutable后,在常函数中依然可以修改。
常对象:
声明对象前加const称该对象为成对象;
常对象只能调用常函数。
class Person{
public:
//常函数:
void printName() const{//内隐含着一个this指针,是指针常量,指向后不能更改指向,但可以修改指向的值;
// 函数名后加一个const修饰this指向,变为:const Person * const this;则不能再修改this指向的值。
// m_name = "wzh";//不可修改
m_age = 10;//添加 mutable 关键字后,在常函数下可以修改此值,修改常函数中成员属性
cout << "名字!" << endl;
}
//常对象如果不是只能调用常函数,会通过调用此函数间接修改成员变量,不可以!
void func(){
m_name = "123";
}
string m_name;
mutable int m_age;
};
void test(){
const Person p;//常对象
// p.m_name = "";//不能修改成员变量的值
p.m_age = 10;//在常对象中也可以修改mutable修饰的成员变量。
p.printName();
}
int main(){
test();
}
14.友元:有些私有属性需要让类外特殊的一些函数或者类进行访问,需要用到友元,目的是让一个函数或者类访问另一个类中的私有成员。
三种实现:全局函数做友元;类做友元;成员函数做友元。
(1)全局函数做友元:
class Building{//房屋类
friend void friends(Building * building);//此全局函数是Building的友元,可以访问Building的私有成员
public:
Building(){
m_sitRoom = "客厅";
m_bedRoom = "卧室";
}
public:
string m_sitRoom;
private:
string m_bedRoom;
};
void friends(Building * building){
cout << "好朋友可以访问我的客厅!" << building->m_sitRoom<< endl;
cout << "好朋友可以访问我的客厅!" << building->m_bedRoom <<endl;//此处可以通过友元声明来访问私有属性
}
void test(){
Building building;
friends(&building);
}
(2)友元类:
//——————以下使用了"类外实现成员函数和构造函数"——————//
class Home;//声明这个类,不然报错
//——————以下定义两个类,Home和Friends类——————//
class Friends{//朋友类
public:
Friends();//构造函数
void visit();//成员函数,访问Home中的公、私有属性
Home * home;
};
class Home{//家类
friend class Friends;//声明友元类Friends,可以访问本类中的私有成员
public:
Home();
public:
string m_sitRoom;//客厅
private:
string m_bedRoom;//卧室
};
//——————类外实现三个构造函数和成员函数,声明是Home类/Friends类下的成员函数——————//
Home::Home(){
m_sitRoom = "客厅";//可以添加this
m_bedRoom = "卧室";
}
Friends::Friends(){
home = new Home;
}
void Friends::visit() {
cout << "朋友正在访问Home中的" << home->m_sitRoom << endl;
cout << "朋友正在访问Home中的" << home->m_bedRoom << endl;
}
//————————————————以下函数调用——————————————————————//
void test(){
Friends friends;
friends.visit();
}
int main(){
test();
}
(3)成员函数作友元:
//——————以下使用了"类外实现成员函数和构造函数"——————//
class Home;
class Friends{
public:
Friends();
void visit1();
void visit2();
Home * home;
};
class Home{
//告诉编译器,Friends类下的的visit1()成员函数作为本类的友元
friend void Friends::visit1();
public:
Home();
public:
string m_sitRoom;
private:
string m_bedRoom;
};
Home::Home(){
m_sitRoom = "客厅";
m_bedRoom = "卧室";
}
Friends::Friends(){
home = new Home;
}
void Friends::visit1(){
cout << "visit:" << home->m_sitRoom << endl;
cout << "visit:" << home->m_bedRoom << endl;//声明了友元,就可以访问到私有属性了
}
void Friends::visit2(){
cout << "visit:" << home->m_sitRoom << endl;
}
void test(){
Friends friends;
friends.visit1();
friends.visit2();
}
int main(){
test();
}
十二、运算符重载
1.定义:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
2.加号运算符重载:实现两个自定义数据类型相加的运算。也可以通过函数重载进行两个不同数据类型相加。通过成员函数或全局函数重载 +:
class Person{
public:
int m_a, m_b;
//start————通过成员函数重载 + ——————//
Person operator+(Person &p){
Person temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp;
}
//——————通过成员函数重载 + ————end//
};
//start————通过全局函数重载 + ——————//
Person operator +(Person &p1, Person &p2){
Person temp;
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
//——————通过全局函数重载 + ————end//
void test(){
Person p1;
p1.m_a = 10;
p1.m_b = 10;
Person p2;
p2.m_a = 5;
p2.m_b = 5;
Person p3 = p1 + p2;
//成员函数 本质是:Person p3 = p1.operator+(p2);
//全局函数 本质是:Person p3 = operator+(p1,p2)
cout << "p2.m_a:" << p3.m_a << endl <<"p3.m_b:" << p3.m_b << endl;
}
int main(){
test();
}
3.左移运算符重载:配合友元可以实现输出自定义数据类型。
class Person{
friend ostream & operator<<(ostream &out, Person &p);
public:
Person(int a, int b){
this->m_a = a;
this->m_b = b;
}
//一般不用成员函数重载左移运算符,cout会在右边
private:
int m_a;
int m_b;
};
//————利用全局函数重载左移运算符——————//
ostream & operator<<(ostream &out, Person &p){//返回out,链式编程,cout后可以无限追加输出
out << "m_a:" << p.m_a << endl << "m_b:" << p.m_b;
return out;
}
void test(){
Person p(10, 5);
cout << p;
}
int main(){
test();
}
//输出:
//m_a:10
//m_b:5
4.递增运算符重载:
引例:
int a = 10;
cout << ++a << endl; //输出11,先执行递增,再执行cout
cout << a << endl; //输出11
int b = 10;
cout << b++ << endl;//输出10,先输出b,再执行b++
cout << b << endl;//输出11
class MyInteger{
friend ostream & operator<<(ostream & out,MyInteger myInteger);
public:
MyInteger(){
m_num = 0;
}
//返回引用,为了一直对一个数据递增
MyInteger & operator++(){//前置
m_num ++;//先++
return *this;
}
//这里要返回MyInteger类型的值而不是引用,因为局部对象temp在当前函数
//执行完后被释放,再返回当前对象的引用会报错(但我这里没有报错,原因未知)
MyInteger& operator++(int){//后置++,用int占位符区分后置,避免重定义
MyInteger temp = *this;//先记录当前值,再++,但返回的是递增前的值
m_num ++;
return temp;
}
private:int m_num;
};
ostream & operator<<(ostream & out,MyInteger myInteger){
out << myInteger.m_num;
return out;
}
void test1(){
MyInteger myInteger;
cout << ++myInteger << endl;
cout << myInteger << endl;
}
void test2(){
MyInteger myInteger;
cout << myInteger++ << endl;
cout << myInteger << endl;
}
int main() {
test1();
test2();
}
5.赋值运算符重载:
编译器至少给一个类添加四个函数:
(1)默认构造函数(无参,函数体为空);
(2)默认析构函数(无参,函数体为空);
(3)默认拷贝构造函数,对属性进行值拷贝;
(4)赋值运算符operator=,对属性进行只拷贝。
如果类中有属性指向堆区,做赋值操作时会出现浅拷贝问题。
//编译器默认提供的赋值操作是浅拷贝,如果有数据自己创建在堆区释放数据时会出现重复释放问题
class Person{
public:
Person(int age){
m_age = new int(age);
}
~Person(){
if (m_age != NULL){
delete m_age;
m_age = NULL;
}
}
Person& operator=(Person &p){
// m_age = p.m_age;//这是编译器默认的浅拷贝
//先判断是否有属性在堆区,有的话先释放,再进行深拷贝
if (m_age != NULL){
delete m_age;
m_age = NULL;
}
m_age = new int (*p.m_age);
return *this;//返回自身的解引用
}
int * m_age;
};
void test(){
Person p1(10);
Person p2(20);
Person p3(30);
//赋值操作 将p1的10赋值给p2,但会出现析构函数中的堆数据的重复释放问题:p2释放了一次,p1又释放,利用深拷贝解决问题
p3 = p2 = p1;//要实现这样的操作,重写的函数要返回Person类的引用
cout << "p1年龄:" << *p1.m_age << endl;
cout << "p2年龄:" << *p2.m_age << endl;
cout << "p3年龄:" << *p3.m_age << endl;
}
int main(){
test();
}
6.重载关系运算符:可以让两个自定义类型对象进行对比操作。
class Person{
public:
Person(string name, int age){
m_name = name;
m_age = age;
}
//重载 == 运算符或 != 运算符
bool operator==(Person &p){
if(this->m_name == p.m_name && this->m_age == p.m_age){
return true;
}
return false;
}
string m_name;
int m_age;
};
void test(){
Person p1("wzh", 18);
Person p2("wzh", 18);
if (p1 == p2){
cout << "相等!" << endl;
} else{
cout << "不等!" << endl;
}
}
int main(){
test();
}
7.函数调用运算符重载:()重载后非常像函数的调用,称为仿函数,没有固定写法。
class Print{
public:
void operator()(string test){
cout << test << endl;
}
};
class Add{
public:
int operator()(int num1, int num2){
return num1 + num2;
}
};
void test(){
Print print;
print("hello!");
Add add;
int result = add(10, 5);
cout << "result:"<<result << endl;
//start————创建匿名函数对象——————//匿名对象:Add()
cout << "匿名函数result:" << Add()(10, 20) <<endl;//执行完后立即释放
//——————创建匿名函数对象——————end//
}
int main(){
test();
}
十三、继承
1.继承语法:class 子类:继承方式 父类
2.继承方式:不管以何种方式继承,子类中都不能继承父类的私有属性。
公有继承:除了私有权限,原样继承父类的属性;
保护继承:除了私有权限,将父类的公有和保护属性继承后变为保护属性;
私有继承:将父类的公有和保护属性继承后变为私有属性。
3.继承时子类会继承父类中所有属性(公有、保护、私有)的非静态成员,父类的私有成员是被编译器隐藏了,访问不到。
4.继承中的构造和析构顺序:父类构造函数->子类构造函数->子类析构函数->父类析构函数。
5.当子类和父类出现同名的成员,需要通过子类对象访问子类或父类中同名的成员属性或成员函数时:
(1)访问子类同名成员属性/成员函数直接访问即可;
(2)访问父类同名成员属性/成员函数需要添加作用域:类名.作用域::属性名/类名.作用域::函数名。
注:如果子类出现和父类同名的成员函数,子类的同名成员函数会隐藏父类所有的同名成员函数(包括重载函数)。
6.继承中同名的静态成员在子类对象上的访问:与非静态成员处理方式一致。但有两种访问方式:
(1)访问同名静态成员属性:
<1>通过对象访问:s.m_a s.Base::m_a
<2>通过类名访问:Son::m_a Son::Base::m_a(第一个::代表通过类名方式访问,第二个::代表访问父类的作 用域下的属性)
(2)访问同名静态成员函数:
<1>通过对象访问:s.func() s.Base::func()
<2>通过类名访问:Son::func() Son::Base::func()
7.多继承语法:class 子类:继承方式 父类1, 继承方式 父类2…(不建议使用多继承)
8.多继承时,当父类中出现同名成员会报错,需要加作用域区分。
9.菱形继承(钻石继承):两个子类继承同一个父类,又有某个类同时继承这两个子类。假设父类的一个属性已经被两个子类继承,子子类会又继承了两份此属性。当菱形继承时,子子类有2个子类有相同的数据,需加作用域区分。但子子类只需要一份此数据, 造成资源浪费,解决方式:虚继承——两个子类继承方式前加关键字virtual,此时基类称为虚基类。到此,此数据只有一份,不论在哪个作用域(子类1、子类2、子子类)中操作此值否只有一份。因为在两个子类中各建立了一个虚基类指针vbptr,指向子子类的此属性值。
十四、多态
1.多态分为静态多态和动态多态(主要)。
静态多态:函数重载和运算符重载;
动态多态:派生类和虚函数实现运行时的动态多态。
2.区别:
静态多态的函数地址早确定:在编译阶段确定函数地址;
动态多态的函数地址晚确定:在运行阶段确定函数地址。
3.(1)动态多态代码示例:
//虚函数处理前:输出:动物叫 因为地址在编译阶段已确定地址。
//因为doSpeak()函数接收的是Animal对象,调用speak函数会调用Animal类中的speak()函数
//若需要执行 猫叫,需要晚绑定:则此函数地址不能提前绑定,需要在运行时绑定
//在动物类的speak函数前加virtual关键字,则不提前确定地址
class Animal{
public:
virtual void speak(){//在此处添加virtual关键字变为虚函数
cout << "动物叫" << endl;
}
};
class Cat:public Animal{
public:
void speak(){
cout << "猫叫" << endl;
}
};
void doSpeak(Animal &animal){//Animal & animal = cat;父类的指针可以直接指向子类
animal.speak();
}
int main(){
Cat cat;
doSpeak(cat);
}
(2)动态多态满足条件:<1>有继承关系;<2>子类重写父类的虚函数,子类加与不加virtual关键字无妨。
(3)动态多态使用条件:父类指针或者引用指向子类对象,即上述中doSpeak()函数使用时。
4.多态的原理:
(1)上述代码示例中,如果不使用virtual关键字,Animal只占用1B,是一个空类;
(2)增加了virtual关键字后,占4B,是因为增加了一个指针——vfptr(虚函数指针),指向一个虚函数表(vftable),里面存放的是虚函数表,记录虚函数的地址。
(3)此时Animal类的表内记录了:&Animal::speak
(4)而Cat类也继承了vfptr,其虚函数表内也记录了:&Animal::speak
(5)当重写父类的虚函数后,子类的虚函数表内部会:将继承来的父类虚函数表的内容替换成子类的虚函数地址:&Cat::speak
(6)当父类的指针或引用指向子类对象时,发生多态,编译器会去传入的子类的虚函数表中调用函数的入口地址。
5.多态实现计算器的案例:
//实现计算器抽象类,下面的子类会重写其成员函数
class AbsCalculator{
public:
virtual int getResult(){
return 0;
}
int m_num1;
int m_num2;
};
//加减乘的子类,继承父类,重写getResult()函数
class AddCalculator:public AbsCalculator{
public:
int getResult(){
return m_num1 + m_num2;
}
};
class SubCalculator:public AbsCalculator{
public:
int getResult(){
return m_num1 - m_num2;
}
};
class MulCalculator:public AbsCalculator{
public:
int getResult(){
return m_num1 * m_num2;
}
};
//使用多态计算加法,将父类指针指向子类
void polymAdd(){
AbsCalculator * abc = new AddCalculator;
abc->m_num1 = 1;
abc->m_num2 = 3;
cout << abc->getResult() << endl;
delete abc;
}
void polymSub(){
AbsCalculator * abc = new SubCalculator;
abc->m_num1 = 1;
abc->m_num2 = 3;
cout << abc->getResult() << endl;
delete abc;
}
int main(){
polymAdd();
polymSub();
}
6.纯虚函数和抽象类:
(1)纯虚函数:在多态中,父类中的虚函数实现基本是无意义的,主要都是调用子类重写的内容。因此可将虚函数改为纯虚函数:virtual 返回值类型 函数名 (参数列表) = 0;
(2)抽象类:当类中只要有了一个纯虚函数,此类称为抽象类。
(3)抽象类的特点:无法实例化对象;子类必须重写抽象类中的纯虚函数,否则子类也属于抽象类,无法实例化对象。
7.多态实现制作饮品案例:
描述:过程:煮水->冲泡->倒入杯中->加入辅料,提供抽象类:制作饮品基类,提供子类制作咖啡和茶叶。
class AbsDrink{
public:
virtual void Boil() = 0;//纯虚函数,煮水
virtual void Brew() = 0;//冲泡
virtual void Pour() = 0;//倒水
virtual void Put() = 0;//加辅料
void make(){//开始制作
Boil();
Brew();
Pour();
Put();
}
};
class Coffee:public AbsDrink{
public:
virtual void Boil(){
cout << "加入纯净水" << endl;
}
virtual void Brew(){
cout << "冲泡咖啡" << endl;
}
virtual void Pour(){
cout << "倒入杯中" << endl;
}
virtual void Put(){
cout << "加入方糖" << endl;
}
};
class Tea:public AbsDrink{
public:
virtual void Boil(){
cout << "加入山泉水" << endl;
}
virtual void Brew(){
cout << "冲泡茶叶" << endl;
}
virtual void Pour(){
cout << "倒入茶具中" << endl;
}
virtual void Put(){
cout << "加入柠檬" << endl;
}
};
void doWork(AbsDrink * abs){
abs->make();
delete abs;
}
void makeCoffee(){
doWork(new Coffee);
}
void makeTea(){
doWork(new Tea);
}
int main(){
makeCoffee();
makeTea();
}
8.虚析构和纯虚析构:
使用多态时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决方案:将父类中的析构函数改为虚析构或纯虚析构。
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象问题;
且都需要有具体的函数实现。
区别:有了纯虚析构则该类属于抽象类,无法实例化对象。
示例:父类指针在析构时不会调用子类中的析构函数,导致子类如果有堆区属性出现内存泄漏。
class Animal{
public:
Animal(){
cout << "Animal构造函数调用!" << endl;
}
// virtual ~Animal(){//变为虚析构
// cout << "Animal析构函数调用!" << endl;
// }
virtual ~Animal() = 0;//纯虚析构:需要声明也需要实现
virtual void say() = 0;
};
//纯虚析构函数的实现:
Animal::~Animal(){
cout << "Animal纯虚析构函数调用!" << endl;
}
class Cat:public Animal{
public:
Cat(string name){
cout << "Cat构造函数调用!" << endl;
m_name = new string(name);
}
~Cat(){
if(m_name != NULL){
cout << "Cat析构函数调用!" << endl;
delete m_name;
m_name = NULL;
}
}
virtual void say(){
cout << *m_name << "_猫叫!" << endl;
}
string * m_name;
};
void testcat(){
Animal * animal = new Cat("tom");
animal->say();
//父类指针在析构时不会调用子类中的析构函数,导致子类如果有堆区属性出现内存泄漏
//解决方式:在父类析构函数前加virtual关键字变为虚析构
delete animal;
}
int main(){
testcat();
}
9.电脑组装案例:
(1)将电脑的每个零件封装出抽象基类;
(2)并提供不同的厂商生产的不同的零件;
(3)创建电脑类提供让电脑工作的函数,并调用每个零件工作的接口;
(4)测试时组装三台不同的电脑进行工作。
class Cpu{
public:
virtual void calculator() = 0;
};
class VideoCard{
public:
virtual void print() = 0;
};
class Memory{
public:
virtual void store() = 0;
};
class Computer{
public:
Computer(Cpu * cpu, VideoCard * vc, Memory * memory){
m_cpu = cpu;
m_vc = vc;
m_memory = memory;
}
~Computer(){
if (m_cpu != NULL){//释放cpu的内存
delete m_cpu;
m_cpu =NULL;
}if (m_vc != NULL){//释放显卡的内存
delete m_vc;
m_cpu =NULL;
}if (m_memory != NULL){//释放内存的内存
delete m_memory;
m_cpu =NULL;
}
}
void boot(){//让电脑运行
m_cpu->calculator();
m_vc->print();
m_memory->store();
cout << "启动完成!" << endl;
}
Cpu * m_cpu;
VideoCard * m_vc;
Memory * m_memory;
};
class IntelCpu:public Cpu{
virtual void calculator(){
cout << "Intel的CPU开始计算..." << endl;
};
};
class IntelVideoCard:public VideoCard{
virtual void print() {
cout << "Intel的显卡开始显示..." << endl;
}
};
class IntelMemory:public Memory{
virtual void store() {
cout << "Intel的内存条开始存储..." << endl;
}
};
class DellCpu:public Cpu{
virtual void calculator(){
cout << "Dell的CPU开始计算..." << endl;
};
};
class DellVideoCard:public VideoCard{
virtual void print() {
cout << "Dell的显卡开始显示..." << endl;
}
};
class DellMemory:public Memory{
virtual void store() {
cout << "Dell的内存条开始存储..." << endl;
}
};
void package0(){//组装第1台电脑:多态
//第1台电脑的零件,在堆区开辟内存,在电脑类的析构函数中销毁
Cpu * intelCpu = new IntelCpu;
VideoCard * intelVc = new IntelVideoCard;
Memory *intelMemory = new IntelMemory;
//组装第一台电脑
Computer * computer = new Computer(intelCpu, intelVc, intelMemory);
computer->boot();
delete computer;
}
void package1(){//组装第2台电脑
//第2台电脑的零件,在堆区开辟内存,在电脑类的析构函数中销毁
Cpu * dellCpu = new DellCpu;
VideoCard * dellVc = new DellVideoCard;
Memory *dellMemory = new DellMemory;
//组装第一台电脑
Computer * computer = new Computer(dellCpu, dellVc, dellMemory);
computer->boot();
delete computer;
}
void package2(){//组装第3台电脑
//第3台电脑的零件,在堆区开辟内存,在电脑类的析构函数中销毁
Cpu * intelCpu = new IntelCpu;
VideoCard * dellVc = new DellVideoCard;
Memory *intelMemory = new IntelMemory;
//组装第一台电脑
Computer * computer = new Computer(intelCpu, dellVc, intelMemory);
computer->boot();
delete computer;
}
int main(){
package0();
package1();
package2();
}
十五、文件
1.C++中对文件操作要包含头文件。文件类型分为两种:
文本文件:文件以文本的ASCII码形式存储在计算机中;
二进制文件:文明以文本的二进制形式存储在计算机中
2.操作文件的三大类:
ofstream:写操作;
ifstream:读操作;
fstream:读写操作。
3.文本文件:
(1)写文件步骤:
<1>包含头文件;
<2>创建流对象:ofstream ofs;
<3>打开文件:ofs.open("./xxx", 打开方式);
<4>写数据:ofs << “xxxxx”;
<5>关闭文件:ofs.close();
(2)打开方式:可以配合使用,使用 | 操作符。
ios::in | 读方式 |
---|---|
ios::out | 写方式 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 若文件存在则先删除,再创建 |
ios::binary | 二进制方式 |
(3)写文件示例:
void write(){
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:" << endl;
ofs << "性别:" << endl;
ofs << "年龄:" << endl;
ofs.close();
}
(4)读文件的四种方式:
void read(){
ifstream ifs;
ifs.open("test.txt", ios::in);
if(!ifs.is_open()){
cout << "打开失败!" << endl;
return;
}
//第1种读取方式
char buff0[1024] = {0};
while (ifs >> buff0){
cout << buff0 <<endl;
}
//第2种读取方式
char buff1[1024] = {0};
while (ifs.getline(buff1, 1024)){
cout << buff1 <<endl;
}
//第3种读取方式
string buff2;
while (getline(ifs, buff2)){
cout << buff2 <<endl;
}
//第4种读取方式
char c;
while ((c = ifs.get()) != EOF){
cout << c;
}
ifs.close();
}
4.二进制读写文件:
class Person{
public:
char m_name[64];
int m_age;
};
void write2file(){
ofstream ofs("person.txt", ios::out | ios::binary);
Person p = {"wzh", 20};
ofs.write((const char *)&p, sizeof(Person));
}
void readFile(){
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open()){
cout << "打开文件失败!" << endl;
return;
}
Person p;
ifs.read((char *)&p, sizeof(Person));
cout << "姓名:" << p.m_name << "年龄:" << p.m_age << endl;
}
int main(){
write2file();
readFile();
}