C++程序进阶学习

目录

引言  

C++内存分区

一、内存分区模型

二、 程序运行前

 三、程序执行后

C++引用

引用的语法

作用

本质

优点

C++封装

C++对象特性

一、构造函数

二、析构函数

1. 编译器默认添加的函数

 2. 有参构造函数的影响

 3. 拷贝构造函数的影响

 4. 手动定义所有特殊成员函数

C++对象模型和this指针

C++友元

C++运算符重载

C++继承

C++多态

C++文件


引言  

        看过我博客的朋友可能都了解这篇文章内容了,这篇博客是由我单独每天撰写的C++内容合集,重新整理完善了内容;也算是自己重新学习,也和大家一起学习,如果对大家的学习有帮助,那自然更好,如果大家在学习的过程中发现文章内容有问题或者不懂的,希望大家能在评论区积极讨论,我看到了也会回复!!!

C++内存分区

一、内存分区模型

在执行C++程序时,我们可将内存划分为四个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理(我们一般的代码都在里面)
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:存放函数的参数值,局部变量等,这个区域则由编译器自动分配释放
  • 堆区:这一区域则是程序员自己分配和释放,程序结束时也会由系统回收

为什么要区分这些区域呢? 

将数据放在不同区域,数据所占空间的时间不同,这也使我们能更灵活的使用数据。

二、 程序运行前

在程序编译后,未执行该程序前分为两个部分

1.代码区:

存放CPU执行的机器指令

我们需要注意代码区是只读不写的

2.全局区:

C++程序进阶学习_第1张图片

 三、程序执行后

在程序执行后可分为两部分

栈区:这里我们需要注意的是,在编写程序时,不能返回局部变量的地址

示例:

#include
using namespace std;

//栈区的数据由编译器管理开辟和释放
int* func()//形参数据也放在栈区
{
    int c_a = 10;
    return &c_a;
}

int main()
{
    
    int * p = func();
    cout << *p << endl;//第一次可以打印正确的数字,这个时候他的值以及被编译器释放了,只是因为编译器做了保留
    cout << *p << endl;//第二次这个数据就不再保留

    system("pause");
    return 0;
}

堆区:在C++中主要利用new在堆区开辟内存

示例:

#include
using namespace std;

int* func()
{
    int * p = new int(1);
    return p;
}

int main()
{
    int*p = func();
    cout << *p << endl;
    cout << *p << endl; 
    cout << *p << endl;//只要我们没有人为的去释放内存,是可以一直输出的
    system("pause");
    return 0;
}

C++引用

引用的语法

        数据类型  & 别名 = 原名

作用

        1、函数传参时,可以利用引用技术让形参修饰实参

        2、引用是可以作为函数的返回值存在的(同样不可以返回局部变量引用)

本质

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

优点

        可以简化指针修改实参

示例1:

#include
using namespace std;

int main()

{

int a = 1;
int &b = a;//创建引用
cout << "a=" << a << endl;//输出均是1
cout << "b=" << b << endl;
b = 2;
cout << "a=" << a << endl;//输出均是2
cout << "b=" << b << endl;
system("pause");
return 0;
}

注意:

        1.引用必须要初始化

        2.一旦初始化了,就不可以更改了!!!!

我们也同样可以来看一下引用其本质,下面一个示例就能很好的说明:

示例2:

#include
using namespace std;

//发现是引用,转换为 int* const b = &a;

void func(int &b)

{
b = 100;//ref是引用,转换为*b = 100
}

int main()

{
int a = 10;
//自动转换为 int* const b = &a,这就相当于一个指针常量,指针常量我们都知道是指针指向不可改,这也充分说明了我们之前为什么说引用不可更改
int &b = a;
ref = 20;//当编译器内部发现 ref 是引用,编译器自动将其转换为:*b=20;
cout << "a = " << a << endl;
cout << "b = " << b << endl;

func(a);
system("pause");
return 0;
}

        当我们在编写程序时就无需考虑其是怎样去转换的,讲解引用本质,只是为了帮助我们理解。我们只需掌握引用的语法结构、作用以及注意事项!!! 

C++封装

        C++认为万事万物都皆为对象,对象上有其属性和行为

    类在设计时,可以把属性和行为加以控制,可以设置三种权限:

  1. public      公共权限   成员  类内可以访问  类外可以访问
  2. protected   保护权限   成员  类内可以访问  类外不可以访问
  3. private     私有权限    成员  类内可以访问  类外不可以访问

        这里使用class去创建类,前面我们学习了struct创建体,二者并没有什么差别,struct和class唯一的区别就是默认的访问权限不同:struct默认权限为公共,class默认权限为私有

举一个简单的例子,求圆的周长:

        首先我们进行简单分析:我们知道圆的周长公式为2ΠR,Π是个定数,一般使用3.14,简单定义就行;R就是需要去设置的,这里我们就设计一个类,一个类里面包含属性和行为,圆的属性有圆心、半径或者直径,这里我们只需要设置一个半径就可以了,行为:就是我们要求的内容,设定为一个函数。下面就是代码:

#include
using namespace std;
const double PI = 3.14;

//class 代表设计一个类,类后面紧跟着的就是类的名称
class Circle
{
public:  //公共权限
	//属性
	//半径
	int m_r;
	//行为
	//获取圆的周长
	double calculateZC()
	{
		return 2 * PI * m_r;
	}
};

int main()
{
	//实例化  创建一个对象
	Circle c;
	//给圆对象 的属性进行赋值
	c.m_r = 10;
	cout << "圆的周长:" << c.calculateZC() << endl;
	system("pause");
	return 0;
}

        上面代码我们使用了最简单的类的创建,就是将属性都统一放置在公共权限中,但在我们通常编写程序时将这些放在私有权限,将设置数据和调取数据函数放在公共权限,这样有以下两个优点:

优点1:将所有成员属性设置为私有,可以自己控制读写权限

优点2:对于写权限,我们可以检测数据的有效性

我们同样以求圆周为例:

#include
using namespace std;

//圆周率
const double PI = 3.14;

//class 代表设计一个类,类后面紧跟着的就是类的名称
class Circle
{
public://公共权限
    //  设置半径
	void setR(int r)
	{
		m_R = r;
	}
    // 读取半径
	int getR()
	{
		return m_R;
	}
    //行为
	//获取圆的周长
	double getZC()
	{
		return 2 * PI * m_R;
	}

	//私密权限
private:
	//属性
	//半径
	int m_R;
};

int main()

{
	//实例化 创建具体的圆(对象)
	Circle c;
	//给圆对象 的属性进行赋值
	c.setR(10);
	cout << "圆的周长:" << c.getZC() << endl;
	system("pause");
	return 0;
}

        这样我们就能很好的控制数据的权限,在编写程序时需要分辨这个数据,在主函数中是否需要修改,是否需要读取,只有这样才能尽可能的减少编写错误。

C++对象特性

        在C++中,类的构造函数和析构函数是处理对象初始化和清理的关键。下面是构造函数和析构函数的详细解释:

一、构造函数

构造函数用于对象的初始化。构造函数的特点和使用如下:

  • 函数名与类名相同:构造函数的名称必须和类的名称完全相同,这样编译器才能识别它是构造函数。
  • 没有返回值:构造函数没有返回值类型,因此不需要写void或其他返回类型。
  • 可以有参数,可以重载:构造函数可以有参数,可以根据不同参数进行重载,这使得可以用不同方式初始化对象。
  • 自动调用:在创建对象的时候,构造函数会自动调用,而且每个对象的构造函数只会调用一次。

二、析构函数

析构函数用于对象销毁前的清理操作。析构函数的特点和使用如下:

  • 函数名与类名相同,前加 ~:析构函数的名称与类名相同,但前面要加上波浪号 ~
  • 没有返回值:析构函数也没有返回值类型,因此不需要写void或其他返回类型。
  • 不能有参数,不能重载:析构函数不能有参数,也不能被重载。
  • 自动调用:对象在销毁前会自动调用析构函数,每个对象的析构函数也只会调用一次。

下列写了三类函数的语法,以及三种调用方式:

#include
using namespace std;

class Person
{
public:
	
	//构造函数
	Person()
	{
		cout << "Person的无参构造函数调用" << endl;
	}
	Person(int a)
	{
		age = a;
		cout << "Person的有参构造函数调用" << endl;
	}
	//拷贝构造函数
	Person(const Person &p)
	{
		//将传入的人的身上的所有属性,拷贝到我身上
		cout << "Person的拷贝构造函数调用" << endl;
		age = p.age;
	}
	~Person()
	{
		cout << "Person的析构函数调用 " << endl;
	}
	int age;
};

void test()
{
	//1、括号法
     Person p1;//默认构造函数调用
	 Person p2(10);//有参构造函数
	 Person p3(p2);//拷贝构造函数

	//2、显示法
	Person p1;
	Person p2 = Person(10);//有参构造
	Person p3 = Person(p2);//拷贝构造

	//3、隐式转换法
	Person p4 = 10;//相当于  写了 Person p4 = Person(10)
	Person p5 = p4;
}

int main()
{
	test();
	system("pause");
	return 0;
}

        在C++中,构造函数的调用和生成有一些特定的规则。了解这些规则有助于正确管理类的构造和销毁。以下是详细的说明:

1. 编译器默认添加的函数

当你定义一个类时,C++编译器会自动为类添加至少三个默认函数:

  • 默认构造函数:如果你没有定义任何构造函数,编译器会提供一个无参的默认构造函数。
  • 析构函数:编译器会提供一个默认的析构函数,用于对象销毁时的清理工作。
  • 拷贝构造函数:编译器会提供一个默认的拷贝构造函数,该函数执行成员变量的浅拷贝(值拷贝)。

示例代码:

class MyClass {
    // 如果没有定义任何构造函数、析构函数或拷贝构造函数
    // 编译器会自动生成以下函数:
public:
    MyClass();              // 默认构造函数
    ~MyClass();             // 默认析构函数
    MyClass(const MyClass& other); // 默认拷贝构造函数
};

 2. 有参构造函数的影响

  • 覆盖默认构造函数:如果你定义了一个有参构造函数,编译器就不再提供默认的无参构造函数。
  • 依然提供拷贝构造函数:即使定义了有参构造函数,编译器仍然会提供默认的拷贝构造函数(如果没有显式定义)。

示例代码:

class MyClass {
public:
    MyClass(int value);    // 有参构造函数

    // 编译器不会再提供默认构造函数
    // 编译器依然会提供默认的拷贝构造函数和析构函数
};

 3. 拷贝构造函数的影响

  • 覆盖默认拷贝构造函数:如果你定义了拷贝构造函数,编译器就不再提供默认的拷贝构造函数。
  • 不影响其他普通构造函数:即使定义了拷贝构造函数,编译器仍然会提供默认构造函数和析构函数(如果没有显式定义)。

示例代码:

class MyClass {
public:
    MyClass(const MyClass& other);  // 拷贝构造函数

    // 编译器不会再提供默认的拷贝构造函数
    // 编译器依然会提供默认构造函数和析构函数(如果没有显式定义)
};

 4. 手动定义所有特殊成员函数

        在实际开发中,通常会根据需要手动定义所有的特殊成员函数,以确保对象的正确构造和销毁。

示例代码:

class MyClass {
public:
    MyClass() {
        // 自定义默认构造函数
    }

    MyClass(int value) {
        // 自定义有参构造函数
    }

    MyClass(const MyClass& other) {
        // 自定义拷贝构造函数
    }

    ~MyClass() {
        // 自定义析构函数
    }
};

        通过理解这些规则,可以更好地控制对象的生命周期,避免潜在的内存泄漏和其他问题。 

C++对象模型和this指针

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

只有非静态成员变量属于类的对象上

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

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

我们用下面这个示例来说明上面知识点:

#include
using namespace std;

class Person
{
	int m_A;//只有非静态成员变量 
	static int m_B; //静态成员变量    静态变量,类内定义,类外赋值

	void func()//非静态成员函数
	{

	}
	void func1(){}//静态成员函数 
};
int Person::m_B = 1;

void test()//测试空对象所占位置
{
	Person p;
	
	cout << "size of p = " << sizeof(p) << endl;

}

void test2()
{
	Person p;
	cout << "size of p = " << sizeof(p) << endl;

}
int main()
{
	test();
	test2();
	system("pause");
	return 0;
}

 前面我们说了成员变量和成员函数是分开存储的,那么我们怎样区分调用的呢?

这里就出现了this指针,this指针指向被调用的成员函数所属对象,this指针不需要定义,直接使用即可,this指针的本质  是指针常量  指针的指向是不可以修改的

this指针用途:

1、当形参和成员变量同名时,可用this指针来区分

2、在类的非静态成员函数中返回对象本身,可使用return *this

我们编写一段代码实现他的用途:

#include
using namespace std;

class Person
{
public:
	Person(int age)
	{
		//this指针指向 被调用的成员函数 所属对象
		this->age = age;

	}
	
	Person & PersonAddAge(Person &p)
	{
		this->age += p.age;

		//this指向的p2的指针,而*this指向的就是p2这个对象本体
		return *this;
	}

     int age;
};

//1、解决名称冲突
void test1()
{
	Person p1(18);
	cout << "p1的年龄为:" << p1.age << endl;
}

//2、返回对象本身用*this
void test2()
{
	Person p1(10);

	Person p2(10);

	//链式编程思想
	p2.PersonAddAge(p1).PersonAddAge(p1);

	cout << "p2的年龄为:" << p2.age << endl;
}

int main()
{
	test1();
	test2();
	system("pause");
	return 0;
}

 最后再讲两个概念

常函数:

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

常对象:

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

后续我将逐步整合,收藏不迷路!!!大家感兴趣,也可以先看看我之前的博客!!!

C++友元

        在前面我们讲解了成员属性有两种权限设置:公共权限(public)、保护权限(protected)和私有权限(private)

        但是在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问。

那我们该怎样去处理呢?

        现在我们设想一种环境,在你的房间,有柜子放一些书籍,大家都可以看(public),但是有个柜子放你写的日记,一般不给人看(private)。但是你特别好的朋友(friend)来了,这些内容他是可以看的。

        这就是我们要学习的友元,关键字为 friend

        设计他的目的就是让一个函数或者类访问另一个类中私有成员。

友元具有以下三点实现

1、全局函数做友元

2、类做友元

3、成员函数做友元

下面我将用代码实现前两种情况,第三种实现,可以自己试着尝试,如果不懂得可以私信发代码!

示例1:

#include
using namespace std;
#include

class Building
{
	//goodFriend全局函数是Building好朋友,可以访问私有成员
	friend void goodFriend(Building *building);
public:
	Building()
	{
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";

	}

public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室

};

//全局函数
void goodFriend(Building *building)
{
	cout << "访问好友的全局函数:" << building->m_SittingRoom << endl;
	cout << "访问好友的全局函数:" << building->m_BedRoom<< endl;
}

void test()
{
	Building building;
	goodFriend(&building);
}


int main()
{
	test();

	system("pause");
	return 0;
}

 示例2:

#include
using namespace std;
#include

//类做友元
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;
}

void test01()
{
	GoodGay g;
	g.visit();
}


int main()
{
	test01();

	system("pause");
	return 0;
}

C++运算符重载

概念:对已有的运算符重新进行定义,赋予其另一种功能,使其适应不同的数据类型。

下面我们学习六类运算符重载,分别是加号、左移、递增、赋值、关系和函数调用运算符重载。

首先我们学习加号运算符重载

作用:实现两个自定义数据类型相加运算

int a=10;  int b=10;  int c=a+b;这一段是没问题的,那下面这一段可以直接相加吗?

Person p1;  p1.m_A = 10;  p1.m_B = 10;

Person p2;  p2.m_A = 10;  p2.m_B = 10;

Person p3=p1+p2;

这样显然是不行的,编译器不知道怎样去处理这些数据。

这就需要运用加号运算符重载。

示例1:

#include
using namespace std;

//加号运算符重载

class Person
{
public:
	1、成员函数重载+号
	//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;
	//}
	
	int m_A;
	int m_B;
};

//2、全局函数重载+号
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;
}

//函数重载的版本
Person operator+(Person &p1, int num)
{
	Person temp;
	temp.m_A = p1.m_A + num;
	temp.m_B = p1.m_B + num;
	return temp;

}

void test01()
{
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;
	Person p2;
	p2.m_A = 10;
	p2.m_B = 10;

	Person p3 = p1 + p2;
	//运算符重载  也可以发生函数重载
	Person p4 = p1 + 100;

	cout << "p3.m_A = " << p3.m_A << endl;
	cout << "p3.m_B = " << p3.m_B << endl;

	cout << "p4.m_A = " << p4.m_A << endl;
	cout << "p4.m_B = " << p4.m_B << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

 成员函数重载本质调用

Person p3 = p1.operator+(p2);

全局成员重载本质调用

Person p3 = operator+(p1, p2);

了解其本质更有助于我们去理解。

这段代码,可以使用成员函数重载也可以使用全局变量。但不是所有运算符重载都有两种方式。我们接着往后看。

下面讲解左移运算符重载,我们先来思考一下,如果同样运用成员函数编写看看是否可行?

void operator<<(cout)

{

}

利用成员函数重载   左移运算符  p.operator<<(cout)  简化版本 则为 p<

因此不会利用成员函数重载<<运算符,因为无实现 cout在左侧。

示例2:

#include
using namespace std;

class Person
{
public:
	int m_A;
	int m_B;
};

//只能利用全局函数重载运算符
ostream & operator<<(ostream &cout,Person &p)  
{
	cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
	return cout;
}

void test01()
{
	Person p;
	p.m_A = 10;
	p.m_B = 10;

	cout << p << endl;
	
}

int main()
{
	test01();
	system("pause");
	return 0;
}

 前面我们说了加法运算符和左移运算符重载,继续讲解递增、赋值、关系和函数调用运算符重载。

递增运算符重载作用:通过重载递增运算符,实现自己的整型数据。

我们知道递增分为前置++和后置++,因此我们在重载时也要区分他们,前置使用operator++(),而且最后返回的是一个引用,而后置++则是operator++(int),这个地方的int,就是我们前面所讲的占位参数,最后返回的是一个值,我们以此来区分前置和后置,下面来看示例。

示例3:

#include
using namespace std;

class MyInteger
{
	friend ostream &operator<<(ostream &cout, MyInteger myint);
public:
	MyInteger()
	{
		m_Num = 0;
	}
	//重载前置++运算符  返回引用为了一直对一个数据进行递增操作
	MyInteger &operator++()
	{
		//先进行++运算
		m_Num++;
		//再将自身做返回
		return *this;
	}

	//重载后置++运算符
	MyInteger operator++(int)
	{
		//先  记录当时结果
		MyInteger temp = *this;
		//后   递增
		m_Num++;
		//最后将记录结果返回
		return temp;
	}
	//前置返回引用,后置返回值
private:
	int m_Num;

};

//重载<<运算符
ostream &operator<<(ostream &cout, MyInteger myint)
{
	cout << myint.m_Num;
	return cout;
}

void test01()
{
	MyInteger myint;
	cout << ++myint << endl;
	cout << myint << endl;
}

void test02()
{
	MyInteger myint;
	cout << myint++ << endl;
	cout << myint << endl;
}

int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

 下面讲解赋值运算符重载

前面我们学习了C++编译器至少给一个类添加4个函数

1、默认构造函数(无参,函数体为空)

2、默认析构函数(无参,函数体为空)

3、默认拷贝构造函数,对属性进行值拷贝

4、赋值运算符operator=对属性进行值拷贝

前面三种我们都见识了,下面讲解第四类,如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题。堆区,大家还记得吗?用一个关键词  new去创建的。下面我们同样来看一段代码。

示例4:

#include
using namespace std;

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)
	{

		//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}

		//深拷贝
		m_Age = new int(*p.m_Age);

		//返回对象本身
		return *this;

	}

	int *m_Age;
};

void test01()
{
	Person p1(18);
	Person p2(20);
	Person p3(30);

	p3 = p2 = p1;//赋值操作

	cout << "p1的年龄为:" << *p1.m_Age << endl;

	cout << "p2的年龄为:" << *p2.m_Age << endl;
	cout << "p3的年龄为:" << *p3.m_Age << endl;
}


int main()
{
	test01();
	system("pause");
	return 0;
}

 如果这段代码有不明白的地方,可以去看看前面前面深浅拷贝的内容。

接着看关系运算符重载,可以让两个自定义类型对象进行对比操作。

示例5:

#include
using namespace std;

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 test01()
{
	Person p1("Tom", 18);
	Person p2("Jerry", 18);

	if (p1 == p2)
	{
		cout << "p1 和 p2 是相等的!" << endl;
	}
	else
	{
		cout << "p1 和 p2 是不相等的!" << endl;
	}

}

int main()
{
	test01();

	system("pause");
	return 0;
}

最后一个就是函数调用重载,由于使用起来非常类似于函数调用,因此称为仿函数,而去使用非常灵活,没有固定写法。

示例4:

 

#include
using namespace std;
#include
class MyPrint
{
public:
	//重载函数调用运算符
	void operator()(string test)
	{
		cout << test << endl;
	}
};

void MyPrint02(string test)
{
	cout << test << endl;
}

void test01()
{
	MyPrint myPrint;
	myPrint("hello world");//由于使用起来非常类似于函数调用,因此称为仿函数
	MyPrint02("hello world");
}

//加法类
class MyAdd
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

void test02()
{
	MyAdd myAdd;
	int ret = myAdd(100, 100);
	cout << "ret = " << ret<

 以上内容就是运算符重载全部内容了,代码有问题的,请复习前面所学知识。

C++继承

        继承,其实这个概率我们很好去理解,比如说动物类,他里面包含了猫类、狗类,而猫类里面又分为加菲猫、波斯猫,他们其中都包含着一些相同的属性,在这种情况,我们就可以考虑利用继承的技术来减少重复的代码量。

继承语法:class 子类:继承方式 父类

其中继承方式有分为三类:公共继承、保护继承、私有继承

C++程序进阶学习_第2张图片

当子类和父类出现同名的成员时,我们该如何访问到子类或父类中同名的数据呢?

其实很简单,记住下面两点就行了:

  1. 访问子类同名成员  直接访问即可
  2. 访问父类同名成员  需要加作用域

 示例1:

#include
using namespace std;

class Base
{
public:
	Base()
	{
		m_A = 100;
	}
	void func()
	{
		cout << "Base - func()调用" << endl;
	}

	void func(int a)
	{
		cout << "Base - func(int a)调用" << endl;
	}
	int m_A;
};
class Son :public Base
{
public:
	Son()
	{
		m_A = 200;
	}

	void func()
	{
		cout << "Son - func()调用" << endl;
	}
	int m_A;
};

//同名成员属性
void test01()
{
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	//如果通过子类对象访问父类中同名成员,需要加作用域
	cout << "Base 下 m_A = " << s.Base::m_A << endl;
}

//同名成员函数
void test02()
{
	Son s;
	s.func(); //直接调用 调用是子类中的同名成员
	s.Base::func();//需要加作用域
	//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
	//如果想访问到父类中被隐藏的同名成员函数,需要加作用域
	s.Base::func(2);

}
int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

 注意:

当子类与父类拥有相同成员函数时,子类只是隐藏了父类中同名成员函数,加作用域就可以访问。

有的人可能会想,我一个子类能不能同时继承两个父类呢?

答案是肯定的,可以同时继承多个,但是这种语法我们很少用到

了解其语法就行:语法: class子类 :继承方式  父类1,继承方式 父类2.....

C++多态

多态是面向对象三大特征之一,前面我们已经学习了封装、继承,接下来我们就学习多态。

多态分为两类:

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

我们也主要学习动态多态,我们要注意动态多态的函数地址晚绑定,运行阶段确定函数地址。

动态多态满足条件

1、有继承关系

2、子类重写父类的虚函数

重写:函数返回值类型  函数名  参数列表 完全一致称为重写

示例1:

#include
using namespace std;

//动物类
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 & animal = cat ;
{
	animal.speak();
}

void test01()
{
	Cat cat;
	doSpeak(cat);

	Dog dog;
	doSpeak(dog);
}

void test02()
{
	cout << "sizeof Animal = " << sizeof(Animal) << endl;
}
int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

 在多态函数中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数

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

只要有一个纯虚函数,这个类称为抽象类

抽象类特点:

1、无法示例化对象

2、抽象类的子类  必须要重写父类中的纯虚函数,否则也属于抽象类

示例2:

#include
using namespace std;
#include

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

class Son :public Base
{
public:
	virtual void func()
	{
		cout << "func函数调用" << endl;
	}
};
void test01()
{
	//Base b;//抽象类是无法实例化对象
	//new Base;
	//Son s;//子类必须重写父类中纯虚数,否则无法实例化对象
	Base*base = new Son;
	base->func();
}
int main()
{
	test01();

	system("pause");
	return 0;
}

C++文件

程序运行时产生的数据都属于临时数据,程序一旦关闭就释放了,而通过文件就能解决这个问题。

C++中对文件进行操作需要包含头文件

文件类型分为两类:文本文件、二进制文件

ofstream:写操作

ifstream: 读操作

fetream:读写操作

首先看一下文本文件的建立示例:

#include
using namespace std;
#include

//文本文件  写文件
void test01()
{
	//1、包含头文件 fstream

	//2、创建流对象

	ofstream ofs;
	//3、指定打开方式
	ofs.open("test.txt", ios::out);//(“文件路径”,打开方式)

	//4、写内容
	ofs << "姓名:张三" << endl;
	ofs << "年龄:18" << endl;
	ofs << "性别:男" << endl;

	//5、关闭文件
	ofs.close();
}

int main()
{
	test01();

	system("pause");
	return 0;
}

这里需要讲解一下打开方式,有六中方式:

ios::in     为读文件而打开文件

ios::out    为写文件而打开文件

ios::ate    初始位置:文件尾

ios::app     追加方式写文件

ios::trunc    如果文件存在先删除,再创建

ios::binary   二进制方式

读文件与写文件类似我就不多赘述了

下面讲解二进制的读取

#include
using namespace std;
#include

//二进制文件  读文件

class Person
{
public:
	char m_Name[64];//姓名
	int m_Age;//年龄
};

void test01()
{
	//1、包含头文件

	//2、创建流对象

	ifstream ifs;
	//3、打开文件  判断是否打开成功
	ifs.open("person.txt", ios::in | ios::binary);
	if (!ifs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}

	//4、读内容
	Person p;
	ifs.read((char*)&p, sizeof(Person));

	cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;


	//5、关闭文件
	ifs.close();
}


int main()
{
	test01();

	system("pause");
	return 0;
}

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

如前面示例写的 ios::in | ios::binary。

你可能感兴趣的:(C++,c++,学习,开发语言)