1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > C++语法基础(一)引用 指针 类与对象 多态 文件IO

C++语法基础(一)引用 指针 类与对象 多态 文件IO

时间:2018-07-13 04:13:18

相关推荐

C++语法基础(一)引用 指针 类与对象 多态 文件IO

文章目录

语法格式头文件与宏定义引用引用传参引用作为函数的返回。引用的本质常量引用 函数提高函数默认值函数占用参数函数重载 类与对象访问权限构造函数和析构函数构造函数的分类和调用拷贝函数调用的时机构造函数的调用规则深拷贝和浅拷贝初始化列表类对象作为类成员静态成员成员变量和成员函数分开存储this指针const修饰成员函数友元运算符重载继承多态纯虚函数与抽象类类与继承中的构造与析构虚析构和纯虚析构 数据类型数据类型取值与内存占用typedef声明新类型enum枚举类型 指针指针定义与使用指针与数组指针与函数const修饰指针结构体指针 产生随机数C++文件操作写文件读文件

语法格式

头文件与宏定义

// 引入头文件# include<头文件名>// 宏定义# define PI 3.1415

引用

int a = 10;// 1、b引用a,引用必须要初始化// int &b; // 错误// 2、引用在初始化后,不可用再改变int &b = a;int c = 20;b = c; // 这里不是更改引用,而是赋值操作cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;

结果:

a = 20

b = 20

c = 20

可见引用操作相当于地址操作。

引用传参

引用作为函数参数传递称为引用传递,引用传递与地址传递一样会改变实参

引用作为函数的返回。

// 1、不要返回局部变量的引用// 这个与局部变量一样,存放在栈区,会自动清除int & test01(){int a = 10; //局部变量return a;}// 2、函数的调用可以作为左值// 静态变量,存放在全局区,全局区的数据在程序结束后由体统释放int & test02(){static int a = 10; // 静态变量,存放在全局区,全局区的数据在程序结束后由体统释放return a;}int main(){int &ref2 = test02();cout << "ref2 = " << ref2 << endl;cout << "ref2 = " << ref2 << endl;test02() = 1000;cout << "ref2 = " << ref2 << endl;cout << "ref2 = " << ref2 << endl;}

结果:

ref2 = 10

ref2 = 10

ref2 = 1000

ref2 = 1000

流程 :

ref2 引用全局变量,因此前边两个返回值为10

test02() = 1000

修改全局变量中的ref2的值为1000,main中的ref2值随之改变。

注:这里的引用是main中的ref2引用了方法中ref2,相当于别名与原名相同。

引用的本质

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

即指针中的常量,指向不可改,指针指向的内容可以修改。

这也是为什么引用初始化后不可以再修改的原因,而引用是可以赋值的,即对应指针常量指向的内容可以修改。

常量引用

// 常量引用,即常量指针常量,指向和内容都不可以修改// 使用场景:用来修饰形参,防止误操作// int a = 10;// int & ref = 10; // 会提示错误,因为引用必须引一块合法的内存空间,常量10,在栈区// const int & ref = 10; // 加上const之后,编译器将代码修改为int temp = 10; const int & ref = temp; 即在全局区开辟了// 一块内存,然后引用,此时语法正确。// 注:加入cosnt之后变为只读,不可以修改, // 引用的本质是指针常量,再加const修饰就是指向和访问内容都不可修改// 打印数据函数,采用引用形参,指针常量void showValue(const int &val){// 常量引用可以防止在函数体内修改了valcout << "val = " << val << endl;}int main(){int a = 100;showValue(a);return 0;}

函数提高

函数默认值

C++中函数函数是可以有默认参数的

int func(int a, int b = 20, int c = 30){return a + b + c;}// 如果传入了参数则使用传递的参数,如果没有缺省的,则使用默认值

注意事项

如果某个位置有了默认参数,那么这个位置往后都必须有默认值函数声明和函数实现只能有一个有默认值,否则会导致编译器混淆默认值的使用。

函数占用参数

占用参数也可以有默认值

void func(int a, int){cout << "this if func" << endl;}

函数重载

作用:函数名可以相同,提高复用性

函数重载满足的条件:

函数作用域相同函数名称相同函数参数类型,个数,或者顺序不同

注:函数返回值不可以作为函数重载的条件

函数重载的注意事项

4. 当函数重载碰到默认参数,则可能出现二义性,会报错,尽量不使用

5. 加const和不加const可以作为函数重载的条件

类与对象

访问权限

一个类包含属性和行为,属性和行为都可以称为类的成员。

public 成员类内可以访问,类外也可以访问protected 成员类内可以访问,类外不可访问,儿子可以访问父亲中的保护内容private 成员类内可以访问,类外不可访问,儿子不可以访问父亲中的私有内容

protected与private的区别在于继承时,私有内容子类不可访问,保护内容子类可以访问

C++中struct的默认权限的公共,class的默认权限是私有

构造函数和析构函数

构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用

构造函数语法类名(){}

1.构造函数没有返回值也不写void

2.函数名称与类名相同

3. 构造函数可以由参数,因此可以发生重载

4. 程序在调用时自动调用构造函数,无需手动调用,且只会被构造一次。

析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

析构函数语法:~类名(){}

5. 析构函数,没有返回值也不写void

6. 函数名称与类名相同在名称前加上符号~

7. 析构函数不可以有参数,因此不能发生重载

8. 程序在对象销毁前自动调用析构函数,无需手动调用,且只会调用一次 。

构造和析构都是类必须有的方法,如果不写编译器会提供它们的空实现。

构造函数的分类和调用

按照参数分类,分为无参构造(默认构造是无参的)和有参构造。

按照类型分类,分为普通构造和拷贝构造。

拷贝构造需要将对象本身以引用的方式传递参数,且不可改变(以const修饰)

Person (const Person &p ){age = p.age;}

构造函数的调用方式

括号法

调用有参构造函数时使用,无参构造时不需要加括号,以避免编译器将调用误识别为函数的声明。

Person p1; // 无参构造Person p2(10); // 有参构造Person p3(p2); // 拷贝构造

显示法

Person p1; // 无参构造Person p2 = Person(10); // 有参构造Person p3 = Person(p2); // 拷贝构造

隐式转换法

Person p4 = 10;// 相当于写了Person p4 = Person(10);

拷贝函数调用的时机

C++调用拷贝函数的时机

使用一个已经创建完毕的对象来初始化一个新对象值传递的时候给函数参数传值(即实参向形参赋值的时候)值的方式返回局部对象(即作为函数的返回值返回时)

构造函数的调用规则

默认情况下,C++编译器在创建一个类的时候会至少添加三个函数,默认构造,默认析构和拷贝构造。

如果用户定义了有参构造,则不会在添加默认构造,但仍然会添加拷贝构造,如果用户定义了拷贝构造,则不会添加默认构造和拷贝构造。

深拷贝和浅拷贝

深浅拷贝的区别:

1、浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝(例:assign())

2、深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝。

在代码中浅拷贝只是赋值相等,而深拷贝会自己在堆区开辟新的内存指向赋值。

初始化列表

class Person{// 传统初始化操作// 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){}int m_A;int m_B;int m_C;}

类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员,当其他类对象作为本类成员时,先执行类对象的构造函数,然后执行类本身的构造函数。而析构正相反,先执行类本身的析构,再执行类对象成员的析构。

静态成员

静态成员就是在成员变量和成员函数前加上关键字static。

静态成员变量 所有对象共享同一份数据在编译阶段分配内存(全局区)类内声明,类外初始化

class Person {public:static int m_A; // 类内声明static成员};Person:: m_A = 100; // 类外初始化static成员的值void test01(){Person p;cout << p.m_A << endl; //输出100,因为初始化了Person p2;p2.m_A = 200;cout << p.m_A << endl; // 输出200,因为所有对象共享同一份数据,p2将其修改为了200}void test02(){// 静态成员变量不属于某个对象,所有对象都共享同一份数据,因此静态成员变量有两种访问方式//1、通过对象进行访问// Person p;// cout << p.m_A << endl;// 2、通过类名进行访问// cout<< Person::m_A << endl;// 两种方式是一样的。}

注:静态成员变量也是有访问权限的

静态成员函数 所有对象共享同一个函数静态成员函数只能访问静态成员变量

静态成员函数与静态成员变量一样既可以通过对象访问,也可以通过类名访问

静态成员函数也是有访问权限的。

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

空对象占用空间为:1

C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置

每个空对象有独一无二的内存地址

非静态成员变量内存分配属于类的对象上静态成员变量内存分配不属于类对象非静态成员函数内存分配也不属于类对象

this指针

每一个非静态成员函数只会诞生一份函数实例,也就说多个同类型对象会共用一块代码,C++通过提供特殊的对象指针,this指针,来区分到底是那个对象在调用函数。

this指针是隐含每一个非静态成员函数内的一种指针,this指针不需要定义,直接使用。

this指针的用途:

当形参和成员变量同名时,可以用this指针来区分在类的非静态成员函数中返回对象本身,可使用return *thisthis指针的本质是一个指针常量

const修饰成员函数

常函数:

成员函数后加const后我们称这个函数为常函数常函数内不可以修改成员属性成员属性声明加关键字mutable后,在常函数中依然可以修改

常对象:

声明对象前加const称该对象为常对象常对象只能调用常函数。

class Person{// 在成员函数后边加const,修饰的是this的指向,让指向的值不可以修改void showPerson() const{this->m_B = 100; // m_B添加了mutable关键字,因此可以修改// this->m_A =100; // 会提示语法错误因为常函数内不可修改成员属性,因为this实质是指针常量,指向不可修改,而再加了const修饰后,指向内容也不能修改。但是mutable关键字的特殊变量不受影响 }int m_A;mutable m_B;}void test02(){const Person p; // 在对象前加const修饰变为常对象// 常对象只能调用常函数,因为非常函数内可能修改成员属性,这与定义相违背。}

友元

友元的关键字为friend

介于private与public之间,卧室属于私有空间,不对公众开放,但是对闺蜜基友开放,这就是友元

全局函数作友元

class Building{//全局函数goodGay是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; //卧室属于私有属性,类外不能访问}void test01(){Building building;goodGay(&building);}

本例的核心代码friend void goodGay(Building *building);;

友元做类

class Building;class GoodGay{public:GoodGay();void visit(); // 参观函数 访问BuildingBuilding *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; // 私有类得益于友元类可访问}void test01(){GoodGay gg;gg.visit();}

本例的核心代码friend class GoodGay;

成员函数做友元

class Building;class GoodGay{public:GoodGay();void visit(); // 让visit函数可以访问Building中的私有成员void visit2();// 让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;}GoodGay::visit(){cout << "好基友类正在访问:" << building->m_SittingRoom << endl; // 公共类访问cout << "好基友类正在访问:" << building->m_BedRoom << endl; // 私有类访问}GoodGay::visit2(){cout << "好基友类正在访问:" << building->m_SittingRoom << endl; // 公共类访问}void test01(){GoodGay gg;gg.visit();gg.visit2();}

本例的核心代码

// 告诉编译器,GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员。 friend void GoodGay::visit();

运算符重载

对已有运算符重新定义,赋予另外一种功能,以适应不同数据类型。

运算符重载案例

例如

长方形类有两个属性长和宽,想定义长方形对象1和长方形对象2相加为把他们各自的长和宽分别相加。

class Rectangle{public:int m_width;int m_height;}Rectangle r1;r1.m_width = 10;r1.m_height = 10;Rectangle r2;r2.m_width = 10;r2.m_height = 10;

// 可以通过自己写成员函数来实现两个对象属性相加然后返回新的对象

Rectangle RectangleAdd(Rectangle &r){Rectangle temp;temp.m_width = this->m_width + r.m_width;temp.m_height = this->m_width + r.m_width;return temp;}

// 其实编译器给这种做法起了一个通用名称

operator+

Rectangle operator+(Rectangle &r){Rectangle temp;temp.m_width = this->m_width + r.m_width;temp.m_height = this->m_width + r.m_width;return temp;}

当使用编译器提供的通用名称时:

Rectangle r3 = r1.operator+(r2);

可以简写为:

Rectangle r3 = r1 + r2;

当然也可以通过全局函数重载

Rectangle operator+(Rectangle &r1, Rectangle &r2){Rectangle temp;temp.m_width = r1.m_width + r2.m_width;temp.m_height = r1.m_width + r2.m_width;return temp;}

运算符重载也可以发生函数重载

Rectangle operator+(Rectangle &r1,int num){Rectangle temp;temp.m_width = r1.m_width + num;temp.m_height = r1.m_width + num;return temp;}

Rectangle r3 = r1 + 100;

左移运算符重载即<<

作用是可以输出自定义的数据类型

void operator<<(cout){}

如果利用成员函数重载,则调用的时候:

p.operator<<(cout)简化为p<<cout,而我们正常期望的效果是cout<<p

因此我们一般不会用成员函数来重载<<因为无法实现out在左侧。

只能利用全局函数重载左移运算符

void operator<<(cout, p) //

本质operator<<(cout,p)简化为cout<<p

现在的问题是cout是什么数据类型呢?

其实他是一个ostream(输入流对象)

所以我们的重载方法应该写为

ostream & operator<<(ostream &cout,Person &p) //{cout << "m_A=" << p.m_A << "m_B=" <<p.m_B;return cout;}

在使用时:

cout <<p即可输出P的属性

递增运算符++重载

功能需要包含前置递增和后置递增

// 自定义整形class MyInteger{public:MyInteger(){m_Num = 0;}// 重载前置++运算符// 返回引用是为了一直对同一个数据进行操作MyInteger& operator++(){// 先++运算m_Num++;// 再将自身做返回return *this;}// 重载后置++运算符// int代表占位参数,可以用于区分前置和后置递增MyInteger operator++(int){// 先记录当前结果MyInteger temp = *this;// 后递增,将自身属性递增m_Num++;// 最后将结果记录做返回return temp;}private:int m_Num;}void test01(){MyInteger myint;cout << myint << endl;}// 重载<<运算符ostream & operator<<(ostream & cout, MyInteger myint){cout << myint.m_Num;return cout;.}

此例中前置递增返回引用后置递增返回值,后置递增需要使用(int)占位来区别。

赋值运算符重载

C++编译器至少给一个类添加4个函数 默认构造函数(无参,函数体为空)默认析构函数(无参,函数体为空)默认拷贝构造函数,对属性进行值拷贝赋值运算符operator=对属性进行拷贝

如果类中有属性指向堆区,做赋值操作也会出现浅拷贝的问题。

class Person{public:Person(int age){m_Age = new int(age);}int *m_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;}

关系运算符<,>,=重载

class Person{public:Person(string name, int age){m_Name = name;m_Age = age;}string m_Name;int m_Age;// 重载 == 号bool operator==(Person &p){if(this->m_Name == p.m_Name && this->m_Age == p.m_Age){return true;}return false;}// 重载 != 号bool operator!=(Person &p){if(this->m_Name == p.m_Name && this->m_Age == p.m_Age){return false;}return true;}};void test01(){Person p1("Tom", 18);Person p2("Tom", 18);if (p1 == p2){cout << "p1和p2是相等的" <<endl;}}

函数调用运算符()重载 函数调用运算符()也可以重载由于重载后使用的方式非常像函数的调用,因此称为仿函数仿函数没有固定写法,非常灵活

class MyPrint{public://重载函数调用运算符void operator()(string test){cout << test << endl;}}void test01(){MyPrint myPrint;myPrint("hello world") //类似函数调用}

继承

基本语法

class 子类 : 继承方式 父类

继承方式 公共继承保护继承私有继承

public 类外类内都可访问

protected 类外不可访问,类内可以

private 类外不可访问,类内可以

通过public继承父类中public和protected

通过protected继承,父类中的public和protected都变为了protected

通过private继承,父类中的public和protected都变为了private

继承中的对象模型

父类中所有的非静态成员属性都会被子类继承下去

父类中的私有成员属性是被编译器给隐藏了,因此访问不到,但是确实被继承下去了 继承中的构造和析构如下

先构造父类,再构造子类,析构顺序与构造的顺序相反。继承中同名成员管理

如果通过子类对象访问父类中的同名成员,需要加作用域

cout <<"Son 下 m_A = " << s.m_A << endl;cout <<"Base 下 m_A = " << s.Base::m_A << endl;

s.Base::m_A

如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,如果想访问父类中被隐藏的同名成员函数,需要加作用域

继承中静态成员处理方式

回顾静态成员的特点:所有对象的静态成员共享一份数据,因此静态成员访问方式有两种,既可以根据对象访问,也可根据类名访问。

多继承语法

C++允许一个类继承多个类

语法:class 子类:继承方式 父类1, 继承方式 父类2……

多继承可能会引发父类中有同名成员出现,需要加作用域区分

实际开发中不建议多继承

菱形继承

两个子类继承同一个基类,又有某个类继承这两个子类。

解决方法虚继承

在继承之前加上关键字virtual

多态

多态是C++面向对象三大特性(封装、继承、多态)之一

多态分为两类

静态多态:函数重载和运算符重载属于静态多态,复用函数名动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态的区别:

静态多态的函数地址早绑定 – 编译阶段就确定了函数地址动态多态的函数地址晚绑定 – 运行阶段确定函数地址

动态多态满足的条件

有继承关系子类重写父类的虚函数

动态多态使用

父类的指针或者引用来执行子类对象

在下边的例子中:

父类构建虚方法:

virtual void speak()

调用的时候以父类指针或引用接收子类对象void doSpeak(Animal &animal)

class Animal{public:virtual void speak(){cout << "动物在说哈" << endl;}};class Cat : public Animal{public:void speak(){cout << "喵喵喵" << endl;}};class Dog : public Animal{public:void speak(){cout << "汪汪汪" << endl;}};//执行说哈的函数void doSpeak(Animal &animal){animal.speak();}void test01(){Cat cat;doSpeak(cat);Dog dog;doSpeak(dog);}

结果:喵喵喵

汪汪汪

C++开发提倡利用多态设计程序框架,因为多态优点很多,符合开发中对修改进行关闭,对拓展进行开发的原则。

多态计算器类

class AbstractCalculator{public:virtual int getResult(){return 0;}int m_Num1;int m_Num2;};// 加法计算器类class AddCalculator:public AbstractCalculator{public:int getResult(){return m_Num1 + m_Num2;}};// 减法计算器类class SubCalculator:public AbstractCalculator{public:int getResult(){return m_Num1 - m_Num2;}};// 乘法计算器类class MulCalculator:public AbstractCalculator{public:int getResult(){return m_Num1 * m_Num2;}};void test02(){AbstractCalculator *abc = new AddCalculator;abc->m_Num1 = 100;abs->m_Num2 = 100;cout << abc->m_Num1 << "+" << abc->abc->m_Num2 << "=" << abc->getResult() << endl;delete abc; // 用完后销毁AbstractCalculator *abc = new SubCalculator;abc->m_Num1 = 100;abs->m_Num2 = 100;cout << abc->m_Num1 << "-" << abc->abc->m_Num2 << "=" << abc->getResult() << endl;delete abc; // 用完后销毁AbstractCalculator *abc = new MulCalculator;abc->m_Num1 = 100;abs->m_Num2 = 100;cout << abc->m_Num1 << "*" << abc->abc->m_Num2 << "=" << abc->getResult() << endl;delete abc; // 用完后销毁}

纯虚函数与抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数语法virtual 返回值类型 函数名 (参数列表) = 0

当类中有了纯虚函数,这个类也称为抽象类

抽象类的特点

无法实例化对象子类必须重写抽象类中的纯虚函数,否则也属于抽象类

class Base{public:// 纯虚函数virtual void func() = 0;}

virtual void func() = 0;用=0指代纯虚函数

纯虚函数和抽象类是为了让函数接口更加通用

多态案例——制作饮品

class AbstractDring{public:// 煮水virtual void Boil() = 0;// 冲泡virtual void Brew() = 0;// 倒入杯中virtual void PourInCup() = 0;// 加入辅料virtual void PutSomething() = 0;//制作饮品void makeDrink(){Boil();Brew();PoutInCup();PutSomething();}};// 制作咖啡class Coffee:public AbstractDrinking{public:// 煮水virtual void Boil() {cout <<"煮水" << endl;};// 冲泡virtual void Brew(){cout <<"冲泡咖啡" << endl;};// 倒入杯中virtual void PourInCup(){cout <<"倒入杯中" << endl;};// 加入辅料virtual void PutSomething(){cout <<"加入糖和牛奶" << endl;};};// 制作茶叶class Tea:public AbstractDrinking{public:// 煮水virtual void Boil() {cout <<"煮水" << endl;};// 冲泡virtual void Brew(){cout <<"冲泡茶叶" << endl;};// 倒入杯中virtual void PourInCup(){cout <<"倒入杯中" << endl;};// 加入辅料virtual void PutSomething(){cout <<"加入蜂蜜" << endl;};};// 制作咖啡void doWork(AbstractDrinking *abs){abs->makeDrink();delete abs; // 释放内存}void test01(){// 制作咖啡doWork(new Coffee);cout << "----------" << endl;// 制作茶叶doWork(new Tea);}

类与继承中的构造与析构

类本身包含类对象成员时:

成员类的构造->

类的构造->

类的析构->

成员类析构

构造时先零后整,析构时先整后零

比较好理解,先构造好每个成员后才能完成类本身的构造,而析构的时候,一旦某个成员销毁了,则类本身就不存在了,所以是先析构类本身,后析构具体的类成员子类在继承父类时,对于继承的子类:

父类构造->

子类构造->

子类析构->

父类析构

构造时先父后子,析构时先子后父。

这也比较好理解,子类继承于父类,没有父类哪里来的子类,所以先构造父类,后构造子类。析构的顺利依旧保持与构造的顺序相反。

这里就延伸出一个问题,析构的时候需先析构子类后析构父类,而多态时,如果是使用子类指针指向子类对象析构,则先执行子类析构,后析构父类析构,子类析构释放子类内存,父类析构释放父类内存。但是如果是父类指针执行子类对象构造的子类,则只会调用父类析构,那么就会出现当子类中开辟了内存无法被父类析构清理的情况。解决方法是下边的虚析构,因为虚函数在多态时(父类指针直接指向子类)会使用子类的虚函数,就可以调用到子类析构函数来清理子类开辟的内存 。

虚析构和纯虚析构

多态使用(以父类指针直接指向子类)时。如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解放方法:将父类中析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性

可以解决父类指针释放子类对象都需要有具体的函数实现

虚析构和纯虚析构的区别:

如果是纯虚析构,该类属于抽象类,无法实例化对象

class Animal{public:// 纯虚函数virtual void speak() = 0;Animal(){cout << "Animal的构造调用" << }// virtual ~Animal()// {//cout << "Animal的析构调用" << //}// 有了纯虚析构之后,这个类也属于抽象类,无法实例化对象virtual ~Animal() = 0;};// 纯虚构需要在类内声明,在类外实现Animal::~Animal(){cout << "Animal的纯析构调用" << }class Cat:public Animal{public:Cat(string name){cout << "Cat的构造调用" << m_Name = new string(name);}virtual void speak(){cout << *m_Name <<"小猫在说话" << endl;}string *m_Name;~Cat(){if(m_Name != NULL){cout << "Cat的析构调用" << endl;delete m_Name;m_Name = NULL;}}};void test01(){// 这里Animal * animal = new Cat("Tom");animal->speak();// 父类指针在析构的时候不会调用子类中的析构函数,导致子类如果有堆区属性,就会出现内存泄露。delete animal;}

父类子类的构造析构顺序为父类先构造,再子类构造,子类析构,父类再析构

但在多态(以父类指针指向子类)时,父类指针在析构的时候不会调用子类中的析构函数,导致子类如果有堆区属性,就会出现内存泄露。

本例核心virtual ~Animal(){}

虚析构在类内声明和实现。

纯虚析构类内声明类外实现。

虚析构和纯虚析构不是必要的,本例需要是因为在子类中开辟了堆区的内存。

子类析构与父类析构的调用三种情况

1、父类非虚析构,子类析构,采用子类指针指向子类,先执行子类析构,后父类析构,子类析构子类,父类析构父类,不会有问题。

2、父类非虚析构,子类析构,采用父类指针指向子类,只会执行父类析构,此时如果子类有内存开辟则导致内存泄露。

3、父类虚析构,子类析构,采用父类指针指向子类,会先执行子类析构,再父类析构。

数据类型

数据类型取值与内存占用

typedef声明新类型

enum枚举类型

指针

指针定义与使用

指针就是一个地址编号,代表了变量存放的地址。

X86架构,32位机所有的指针都占用4个字节

而64位机所有指针都占用8个字节

int a = 10;int * p = &a; // &是取地址*p = 20; // *是取值,将a修改为20

指针与数组

数组的指针是数组第一个元素存储的地址。

int [] arr = {1,2,3,4,5};int * p = &arr;cout << "利用指针访问第一个元素:" << *p <<endl;p++; // 指针向后偏移cout << "利用指针访问第二个元素:" << *p <<endl;

指针与函数

地址传递

// 值传递,形参不改变实参,即调用了该函数,只在函数内a,b交换了void swap01(int a, int b){int temp = a;a = b;b = temp;}// 地址传递,可以改变实参。void swap02(int *p1, int *p2){int temp = *p1;*p1 = *p2;*p2 = temp;}// 引用传递,可以改变实参。void swap02(int &p1, int &p2){int temp = p1;p1 = p2;p2 = temp;}

const修饰指针

int a = 10;int b = 10;// 1、const修饰指针,指针指向可以修改,指针指向的值不可改,称为常量的指针int * p = &a;const int *p1 = &a;// *p = 20,错误p = &b; // 指针指向可以修改//2、const修饰常量,指针指向的内容可修改,指针指向不可改,称为指针常量int * const p2 = &a;*p2 = 100; //指针指向内容可改// p2 = &b ,错误,指向不可改//3、const既修饰指令又修饰常量,内容及指向都不可改const int * const p3 = &a;//技巧:将*翻译为指针,const翻译为常量,根据const int * 就是常量指针(联系:常量的指针,常量:内容不可改),int * const 就是指针常量(联系:指针中的常量,指向不可改)。看const右侧紧跟着的是指针(type)*还是常量p,是指针就是常量指针,指向可改,指向的内容不可改;是常量就是指针常量,指向不可改,指向内容可改。

结构体指针

结构体以‘.’访问结构体的属性,结构体指针以->访问

struct Student{string name;int age;int score;}struct Student studenta = {"张三",18,99};struct Student * p = &studenta;cout << studenta.name <<endl;cout << p->name << endl;

产生随机数

#include <ctime> //引入时间相关包void main(){srand((unsigned int)time(NULL)); //设置随机数种子为当前系统时间,(unsigned int)强制转换int random = rand() % 61 + 40; // 产生0(+40)-60(+40)随机数}

C++文件操作

通过文件可以将数据持久化

C++对文件操作需要包含头文件<fstream>

文件类型分为两种:

文本文件 – 文件 以文本的ASCLL码形式存储在计算机中二进制文件 – 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

操作文件的三大类:

ofstream:写操作(写出来,output)ifstream:读操作(读进去,input)fstream:读写操作

写文件

包含头文件#include<fstream>创建流对象ofstream ofs;打开文件ofs.open("文件路径",打开方式);写数据ofs << "写入的数据";关闭文件ofs.close();

注:打开方式可以配合使用,利用|操作符

eg : 用二进制方式写入文件:

ios::binary | ios::out

读文件

包含头文件#include<fstream>创建输入流对象ofstream ifs;打开文件ifs.open("文件路径",打开方式);读数据关闭文件ofs.close();

#include<fstream>#include<iostream>using namespace std;void main(){ifstream ifs;ifs.open("test.txt", ios::in);if (!ifs.is_open()){cout << "文件打开失败" << endl;return;}// 读数据/*第一种char buf[1024] = { 0 };while (ifs >> buf){cout << buf << endl;}ifs.close();*//*第二种char buf[1024] = { 0 };// ifs.getline(buf, sizeof(buf),读取到buf,最多读sizeof(buf)个字节while (ifs.getline(buf, sizeof(buf))){cout << buf << endl;}*//** 第三种* string buf;* while(getline(ifs, buf))* {cout << buf << endl;* }* *//*第四种* 一个字符一个字符读取,判断是否到达文件尾部EOF*/char c;while (( c = ifs.get()) != EOF){cout << c;}ifs.close();return;}

读文件可以利用ifstream,或者fstream读文件可以利用ifs.is_open()函数判断文件是否打开成功ifs.close(); 关闭文件

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。