C++学习中

内存分区模型

C++程序在执行时,将内存大方向划分为4个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

内存四区意义:

不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程

程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域

代码区:

存放 CPU 执行的机器指令

代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区:

全局变量和静态变量存放在此.

全局区还包含了常量区, 字符串常量和其他常量也存放在此.

该区域的数据在程序结束后由操作系统释放.

示例:

C++

//全局变量
int g_a = 10;
int g_b = 10;
//全局常量
const int c_g_a = 10;
const int c_g_b = 10;
int main() {

	//局部变量
	int a = 10;
	int b = 10;
	//打印地址
	cout << "局部变量a地址为: " << (int)&a << endl;
	cout << "局部变量b地址为: " << (int)&b << endl;

	cout << "全局变量g_a地址为: " <<  (int)&g_a << endl;
	cout << "全局变量g_b地址为: " <<  (int)&g_b << endl;

	//静态变量
	static int s_a = 10;
	static int s_b = 10;

	cout << "静态变量s_a地址为: " << (int)&s_a << endl;
	cout << "静态变量s_b地址为: " << (int)&s_b << endl;

	cout << "字符串常量地址为: " << (int)&"hello world" << endl;
	cout << "字符串常量地址为: " << (int)&"hello world1" << endl;

	cout << "全局常量c_g_a地址为: " << (int)&c_g_a << endl;
	cout << "全局常量c_g_b地址为: " << (int)&c_g_b << endl;

	const int c_l_a = 10;
	const int c_l_b = 10;
	cout << "局部常量c_l_a地址为: " << (int)&c_l_a << endl;
	cout << "局部常量c_l_b地址为: " << (int)&c_l_b << endl;

	system("pause");

	return 0;
}

打印结果:

1545017602518

总结:

  • C++中在程序运行前分为全局区和代码区
  • 代码区特点是共享和只读
  • 全局区中存放全局变量、静态变量、常量
  • 常量区中存放 const修饰的全局常量 和 字符串常量

程序运行后

栈区:

由编译器自动分配释放, 存放函数的参数值,局部变量等

注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

示例:

C++

int * func()
{
	int a = 10;
	return &a;
}

int main() {

	int *p = func();

	cout << *p << endl;
	cout << *p << endl;

	system("pause");

	return 0;
}

堆区:

由程序员分配释放,若程序员不释放,程序结束时由操作系统回收

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

示例:

C++

int* func()
{
	int* a = new int(10);
	return a;
}

int main() {

	int *p = func();

	cout << *p << endl;
	cout << *p << endl;
    
	system("pause");

	return 0;
}

总结:

堆区数据由程序员管理开辟和释放

堆区数据利用new关键字进行开辟内存

new操作符

C++中利用new操作符在堆区开辟数据

堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete

语法: new 数据类型

利用new创建的数据,会返回该数据对应的类型的指针

示例1: 基本语法

C++

int* func()
{
	int* a = new int(10);
	return a;
}

int main() {

	int *p = func();

	cout << *p << endl;
	cout << *p << endl;

	//利用delete释放堆区数据
	delete p;

	//cout << *p << endl; //报错,释放的空间不可访问

	system("pause");

	return 0;
}

示例2:开辟数组

C++

//堆区开辟数组
int main() {

	int* arr = new int[10];

	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;
	}

	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	//释放数组 delete 后加 []
	delete[] arr;

	system("pause");

	return 0;
}

引用

引用的基本使用

**作用: **给变量起别名

语法: 数据类型 &别名 = 原名

示例:

C++

int main() {

	int a = 10;
	int &b = a;

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	b = 100;

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	system("pause");

	return 0;
}

引用注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变

示例:

C++

int main() {

	int a = 10;
	int b = 20;
	//int &c; //错误,引用必须初始化
	int &c = a; //一旦初始化后,就不可以更改
	c = b; //这是赋值操作,不是更改引用

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;

	system("pause");

	return 0;
}

引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参

优点:可以简化指针修改实参

示例:

C++

//1. 值传递
void mySwap01(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
}

//2. 地址传递
void mySwap02(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

//3. 引用传递
void mySwap03(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}

int main() {

	int a = 10;
	int b = 20;

	mySwap01(a, b);
	cout << "a:" << a << " b:" << b << endl;

	mySwap02(&a, &b);
	cout << "a:" << a << " b:" << b << endl;

	mySwap03(a, b);
	cout << "a:" << a << " b:" << b << endl;

	system("pause");

	return 0;
}

总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单

引用做函数返回值

作用:引用是可以作为函数的返回值存在的

注意:不要返回局部变量引用

用法:函数调用作为左值

示例:

C++

//返回局部变量引用
int& test01() {
	int a = 10; //局部变量
	return a;
}

//返回静态变量引用
int& test02() {
	static int a = 20;
	return a;
}

int main() {

	//不能返回局部变量的引用
	int& ref = test01();
	cout << "ref = " << ref << endl;
	cout << "ref = " << ref << endl;

	//如果函数做左值,那么必须返回引用
	int& ref2 = test02();
	cout << "ref2 = " << ref2 << endl;
	cout << "ref2 = " << ref2 << endl;

	test02() = 1000;

	cout << "ref2 = " << ref2 << endl;
	cout << "ref2 = " << ref2 << endl;

	system("pause");

	return 0;
}

引用的本质

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

讲解示例:

C++

//发现是引用,转换为 int* const ref = &a;
void func(int& ref){
	ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
	int a = 10;
    
    //自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
	int& ref = a; 
	ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;
    
	cout << "a:" << a << endl;
	cout << "ref:" << ref << endl;
    
	func(a);
	return 0;
}

结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了

常量引用

作用:常量引用主要用来修饰形参,防止误操作

在函数形参列表中,可以加const修饰形参,防止形参改变实参

示例:

C++

//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
	//v += 10;
	cout << v << endl;
}

int main() {

	//int& ref = 10;  引用本身需要一个合法的内存空间,因此这行错误
	//加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;
	const int& ref = 10;

	//ref = 100;  //加入const后不可以修改变量
	cout << ref << endl;

	//函数中利用常量引用防止误操作修改实参
	int a = 10;
	showValue(a);

	system("pause");

	return 0;
}

函数提高

函数默认参数

在C++中,函数的形参列表中的形参是可以有默认值的。

语法: 返回值类型 函数名 (参数= 默认值){}

示例:

C++

int func(int a, int b = 10, int c = 10) {
	return a + b + c;
}

//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
	return a + b;
}

函数占位参数

C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

语法:返回值类型 函数名 (数据类型){}

//第二个int表示占位
void func(int a, int){
    cout << "this is func";
}

//占位参数还可以有默认参数
void func(int a, int = 10){
    
}

函数重载

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

函数重载满足条件:

  1. 同一个作用域下
  2. 函数名相同
  3. 函数参数类型不同或者个数不同或者顺序不同

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

void func(){
    
}

void func(int a){
    
}

//顺序不同
void func(int a, double b){
    
}

void func(double a,int b){
    
}

//引用作为重载的条件
void fun(int &a){
    
}

void fun(const int &a){
    
}

int a = 10;
fun(a); //用第一个
fun(10);//用第二个

//函数重载碰到默认函数
void func(int a, int b = 10){
    
}

void func(int a){
    
}

func(2);//错误,出现二义性

类和对象

C++面向对象的三大特性为:封装、继承、多态

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

具有相同性质的对象,我们可以抽象为类。

封装

封装是C++面向对象三大特性之一。

封装的意义:

  • 将属性和行为作为一个整体,表现生活中的事物

    在设计类的时候,属性和行为写在一起,表示事物

    语法:class 类名{ 访问权限:属性/行为};

    #include<iostream>
    #define PI 3.14
    using namespace std;
    
    class Cricle{
    	public:
        //属性
    		int m_r;
        //行为
    		double calculateZC(){
    			return 2 * PI * m_r;
    		}
    };
    
    int main(){
        //实例化对象
    	Cricle c1;
        //用.访问对象里面的属性或行为
    	c1.m_r = 10;
    	cout << "圆的周长为:" << c1.calculateZC() << endl;
    	
    	return 0; 
    }
    
  • 将属性和行为加以权限控制

    类在设计是,可以把属性和行为放在不同的权限下,加以控制。

    访问权限有三种:

    1. public 公共权限

      类内类外都可以访问

    2. protected 保护权限

      类内可以访问,类外不可以,子类可以访问父类

    3. private 私有权限

      类内可以访问,类外不可以,子类不能访问父类

struct 和 class的区别

struct默认权限是public

class默认权限是private

将成员属性设置为私有

  • 优点1:将成员属性设置为私有,可以自己控制读写权限
  • 优点2:对于写权限,我们可以检测数据的有效性

对象的初始化和清理

构造函数和析构函数

C++利用构造函数和析构函数解决对象的初始化和清理问题,这两个函数会被编译器自动调用,完成对象初始化和清理工作。

对象的初始化和清理工作是编译器强制要求我们做的事情,因此如果我们不提供构造函数和析构函数,编译器会提供编译器自带的构造函数和析构函数。

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

    语法:类名(){}

    1. 没有返回值也不写void
    2. 函数名和类名相同
    3. 可以有参数,因此可以发生重载
    4. 程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次

    按照类型分类:

    1. 普通构造函数

    2. 拷贝构造函数

      class person{
          public:
          int age;
          person(const person &p){
              //将P中的拷贝上这个对象上
              age = p.age;
          }
      }
      

    调用方法:

    1. 括号法

      person p2(10);
      

      调用默认构造函数时,不要加(),会被看成是函数声明

    2. 显示法

      person p2 = person(10);
      person(10);//匿名对象 当前执行结束后,系统会自动回收
      //不要用拷贝构造函数初始化匿名对象,person(p3) === person p3
      
    3. 隐式转换法

      person p4 = 10; // person p4 = person(10);
      

    拷贝构造函数的调用时机:

    1. 使用一个已经创建完毕的对象来初始化一个新对象
    2. 值传递的方式给函数参数传值
    3. 以值方式返回局部对象

    构造函数调用时机:

    默认情况下,c++编译器至少给一个类添加3个函数

    1. 默认构造函数
    2. 默认析构函数
    3. 默认拷贝构造函数,对属性值进行拷贝

    构造函数调用规则如下:

    • 如果用户定义有参构造函数,C++不在提供默认无参构造函数,但是会提供默认拷贝构造函数
    • 如果用户定义拷贝构造函数,C++不会再提供其他构造函数

    深拷贝与浅拷贝:

    • 深拷贝:在堆区重新申请空间,进行拷贝操作

    • 浅拷贝:简单的赋值拷贝操作

      如果利用编译器提供的拷贝构造函数,会做浅拷贝操作

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

    语法:~类名(){}

    1. 没有返回值也不写void
    2. 函数名和类名相同,前面加上~
    3. 不可以有参数,因此不可以发生重载
    4. 程序在对象销毁前会自动调用构造,无需手动调用,而且只会调用一次

初始化列表

作用:C++提供了初始化列表语法,来初始化属性

语法:构造函数() : 属性1(值1),属性2(值2)……{}

class person{
    public:
    int m_a;
    int m_b;
    int m_c;
    
    person():m_a(10), m_b(20), m_c(30){
        
    }
    
    person(int a, int b, int c):m_a(a), m_b(b), m_c(c){
        
    }
}

类对象作为类成员

C++中的成员可以是另一个类的对象,我们称该对象为对象成员

class A{
    
}

class B{
    A a;
}

静态成员

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

静态成员分为:

  • 静态成员变量

    • 所有对象共享同一份数据

    • 在编译阶段分配内存

    • 类内声明,类外初始化

    • 静态成员变量不属于某个对象,所有对象都共享同一份数据,因此静态成员变量有两种访问方法

      1. 通过对象进行访问

      2. 通过类名进行访问

        cout << person::m_a << endl;
        
      #include<iostream>
      using namespace std;
      
      class person{
      	public:
      	static int m_a;
      };
      
      int person::m_a = 100;
      
      int main(){
      	
      	person p;
      	cout << p.m_a << endl;
      	
      	return 0;
      } 
      
  • 静态成员函数

    • 所有对象共享同一个函数

    • 静态成员函数只能访问静态成员变量

      class person{
      	public:
      		//静态成员函数 
      		static void func(){
      			cout << "static void func调用" << endl;
      		}
      };
      
    • 静态成员函数有两种访问方法

      1. 通过对象进行访问

        person p;
        p.func();
        
      2. 通过类名进行访问

        person::func();
        

C++对象模型和this指针

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

在C++中,类内的成员变量和成员函数分开存储。

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

  • 空对象占用内存空间为1

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

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

class person{
    public:
    int m_a; //属于类的对象上
    static int m_b; //不属于类的对象上
    void func1(){
        
    }//不属于类的对象上
    
    static void func2(){
        
    }//不属于类的对象上
};

this指针概念

C++中,每个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。

C++通过提供特殊的对象指针,this指针,来解决这一块代码区分是哪个对象调用自己的我呢提,this指针指向被调用的成员函数所属的对象。

this指针是隐含每一个非静态成员函数内的一种指针。

this指针不需要定义,直接使用即可。

this指针的用途:

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

空指针访问成员函数

C++中空指针也可以调用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性。

#include<iostream>
using namespace std;

class person{
	public:
		int m_age;
		
		void ShowClassName(){
			cout << "this is person class" << endl;
		}
		
		void ShowPersonAge(){
            //预防空指针,可以加上一个判断
            if(this == NULL){
                return;
            }
            
			cout << "age = " << m_age << endl;
		}
    //在m_age里面,默认是this->m_age。不能使用showPersonAge,是因为使用的是空指针。找不到对象
    
};

int main(){
	person *p = NULL;
	p->ShowClassName();
	p->ShowPersonAge();
	
	return 0;
}

const修饰成员变量

常函数:

  • 成员函数后加const后我们称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改
#include<iostream>
using namespace std;

class person(){
	public:
		int m_a;
		mutable int m_b;
		
		//this指针的本质是指针常量,指针的指向是不可以修改的
		//在showPerson()后加const,表示const person * const this; 
		void showPerson() const
		{
			//错误 
			m_a = 100;
			//正确
			m_b = 100; 	
		}
}

int main(){
	
}

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
#include<iostream>
using namespace std;

class person(){
	public:
		int m_a;
		mutable int m_b;
	
		void showPerson() const
		{
			m_a = 100;
			m_b = 100; 	
		}
		
		void func(){
			
		}
}

int main(){
	const person p;//在对象前加const,变为常对象
	 //错误 
	p.m_a = 100;
	//正确
	p.m_b = 100;
	//错误 
	p.func();
	//正确
	p.showPerson();
}

友元

在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。

友元的目的就是让一个函数或者类访问另一个类中的私有成员

友元的关键字为friend

友元的三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

全局函数做友元

#include<iostream>
#include<string>
using namespace std;

class Building{
	//显示goodGuy是友元,可以访问私有函数 
	friend void goodGuy(Building *building);
	
	public:
		string m_SittingRoom; //客厅
		
		Building(){
			m_SittingRoom = "客厅";
			m_BedRoom = "卧室";
		}
		
	private:
		string m_BedRoom;//卧室 
		
};

//全局函数
void goodGuy(Building *building){
	cout << "全局函数正在访问:" << building->m_SittingRoom << endl; 
	cout << "全局函数正在访问:" << building->m_BedRoom << endl; 
} 


int main(){
	Building building;
	goodGuy(&building);
	
	return 0;
}

类做友元

#include<iostream>
#include<string>
using namespace std;

class Building{
    //定义友元
	friend GoodGuy;
	public:
		string m_SittingRoom; //客厅
		
		Building();

	private:
		string m_BedRoom;//卧室 
		
};

//类外写成员函数
Building::Building(){
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}

class GoodGuy{
	public:
		Building *building;
		
		//参观函数,访问building中的属性 
		void visit();
		
		GoodGuy(); 
};

GoodGuy::GoodGuy(){
	building = new Building;
}

void GoodGuy::visit(){
	cout << "类正在访问" << building->m_SittingRoom << endl;
    cout << "类正在访问" << building->m_BedRoom << endl;
}


int main(){
	GoodGuy goodguy;
	goodguy.visit();

	return 0;
}

成员函数做友元

#include<iostream>
#include<string>
using namespace std;

class Building;
class GoodGuy{
	
	public:
		Building *building;
		
		GoodGuy();
		
		//让visit函数可以访问Building中的私有成员 
		void visit();
		//让visit2函数不可以访问Building中的私有成员 
		void visit2();
};

class Building{
	
	friend void GoodGuy::visit();
	public:
		string m_SittingRoom; //客厅
		
		Building();

	private:
		string m_BedRoom;//卧室 
};

GoodGuy::GoodGuy(){
	building = new Building;
}

void GoodGuy::visit(){
	cout << "visit正在访问" << building->m_SittingRoom << endl;
    cout << "类正在访问" << building->m_BedRoom << endl;
}

void GoodGuy::visit2(){
	cout << "visit2正在访问" << building->m_SittingRoom << endl;
//    cout << "类正在访问" << building->m_BedRoom << endl;
}

Building::Building(){
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}


int main(){
	GoodGuy gg;
	gg.visit();
	gg.visit2();
	
	return 0;
}

运算符重载

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

加号运算符重载

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

  • 成员函数重载加号

    #include<iostream>
    
    using namespace std;
    
    class person{
    	public:
    		int m_a;
    		int m_b;
    		
    		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;
    		}
    
    };
    
    void test1(){
    	person p1,p2;
    	p1.m_a = 10;
    	p1.m_b = 10;
    	p2.m_a = 10;
    	p2.m_b = 10;
    	
        //成员函数的本质调用
        //person p3 = p1.operator+(p2);
    	person p3 = p1 + p2;
    	cout << p3.m_a << " " << p3.m_b << endl;
    }
    
    int main(){
    	test1();
    	
    	return 0;
    }
    
  • 全局函数重载加号

    #include<iostream>
    
    using namespace std;
    
    class person{
    	public:
    		int m_a;
    		int m_b;
    };
    
    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;
    }
    
    void test1(){
    	person p1,p2;
    	p1.m_a = 10;
    	p1.m_b = 10;
    	p2.m_a = 10;
    	p2.m_b = 10;
    	
        //全局函数本质调用
        //person p3 = operator+(p1, p2);
    	person p3 = p1 + p2;
    	cout << p3.m_a << " " << p3.m_b << endl;
    }
    
    int main(){
    	test1();
    	
    	return 0;
    }
    

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

左移运算符

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

#include<iostream>

using namespace std;

class person{
	public:
		int m_a;
		int m_b;
		
		//cout 是一个输出流的对象 p.operator<<(cout)
		//不会利用成员函数重载左移运算符,因为无法实现cout在左侧 
//		void operator<<(cout){
//			cout << this->m_a << " " << this->m_b << endl;
//		}
};

//只能利用全局函数重载左移运算符
//cout operator<< p
ostream & operator<<(ostream &cout, person &p){
	cout << "m_a = " << p.m_a << " "<< "m_b = " << p.m_b << endl; 
	return cout;
} 

void test1(){
	person p;
	p.m_a = 10;
	p.m_b = 10;
	cout << p << endl; 
}

int main(){
	test1();
	
	return 0;
}

递增运算符

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

#include<iostream>

using namespace std;

class MyInteger{
	friend ostream& operator<<(ostream& cout, MyInteger myint);
	
	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;
};

ostream& operator<<(ostream& cout, MyInteger myint){
	cout << myint.m_Num;
	return cout;
}

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

 int main(){
 	test1();
 	return 0;
 }

赋值运算符重载

C++编译器至少给一个类添加4个函数:

  • 默认构造函数
  • 默认析构函数
  • 默认拷贝看书
  • 赋值运算符operator=,对属性进行值拷贝

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

#include<iostream>

using namespace std;

class person{
	public:
		int *m_age;
		
		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;
			return *this;
		}
};

void test1(){
	person p1(18);
	person p2(20);

	person p3 = p1 = p2;
	
	cout << *p1.m_age << endl;
	cout << *p2.m_age << endl;
	cout << *p3.m_age << endl;
	
}

int main(){
	test1();
	return 0;
}

关系运算符重载

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

#include<iostream>
#include<string>

using namespace std;

class person{
	public:
		string m_name;
		int m_age;
		
		person(string name, int age){
			m_name = name;
			m_age = age;
		}
		
		bool operator==(person &p){
			if(m_name == p.m_name && m_age == p.m_age)
				return true;
			else
				return false;
		}
};

void test1(){
	person p1("tom", 18);
	person p2("tom", 19);
	
	if(p1 == p2){
		cout << "p1与p2相等" << endl; 
	}
	else{
		cout << "p1与p2不相等" << endl;
	}
	
}

int main(){
	test1();
	return 0;
}

函数调用运算符重载

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

using namespace std;

//打印输出类 
class Myprint{
	public:
		void operator()(string test){
			cout << test << endl;
		}
};

void test1(){
	Myprint myprint;
	myprint("HelloWorld");
}

int main(){
	test1();
	return 0;
}

继承

继承的基本语法

继承的好处:减少重复代码

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

  • 子类也称为派生类
  • 父类也称为基类
#include<iostream>

using namespace std;

//公共页面 
class BasePage{
	public:
		void header(){
			cout << "首页、公开课、登入……" << endl;
		}
		void footer(){
			cout << "帮助中心、交流合作……" << endl;
		}
		void left(){
			cout << "java、python……" << endl;
		}
};

//java页面继承BasePage 
class Java: public BasePage{
	public:
		void content(){
			cout << "java学科视频" << endl;
		}
};

void test1(){
	cout << "java下载视频" << endl;
	Java ja;
	ja.content();
	ja.footer();
	ja.header();
	ja.left(); 
}

int main(){
	test1();
	return 9;
}

继承方式

继承方式一共有3种:

  • 公共继承
  • 保护继承
  • 私有继承

继承中的对象模型

  • 父类中所有非静态成员属性都会被子类继承下去
  • 父类中私有属性是被编译器给隐藏了,因此是访问不到,但是确实是被继承下去了

继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

父先构,子先析

继承同名成员处理方式

当子类和父类出现同名的成员时:

  • 访问子类成员,直接访问即可
  • 访问父类成员,需要加作用域

如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数

#include<iostream>

using namespace std;

class Base{
	public:
		int m_a;
		
		Base(){
			m_a = 100;
		}
};

class Son : public Base{
	public:
		int m_a;
		
		Son(){
			m_a = 200;
		}
};

void test1(){
	Son son;
	cout << son.m_a << endl;
    //如果通过子类对象访问父类中同名成员,需要加作用域
	cout << son.Base::m_a << endl;
}

int main(){
	test1();
	return 0;
}

继承同名静态成员处理方法

静态成员和非静态成员出现同名,处理方式一致。

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域

多继承语法

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

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

  • 多继承可能会引发父类中有同名成员出现,需要加作用域区分
  • C++实际开发中不建议使用多继承

菱形继承

概念:

两个派生类继承同一个基类,又有某个类同时继承两个派生类。这种继承被称为菱形继承或者钻石继承

问题:

继承数据可能会产生二义性

两份从派生类继承的基类数据有重复。

  • 利用虚继承解决菱形继承的问题
#include<iostream>

using namespace std;

class Animal{
	public:
		int m_age;
};

//继承之前叫上关键字virtual变成虚继承
//Animal类称为虚基类
class Sheep : virtual public Animal{
};

class Tuo : virtual public Animal{
};

class SheepTuo : public Sheep, public Tuo{
};

void test1(){
	SheepTuo st;
	
	st.Sheep::m_age = 18; 
	st.Tuo::m_age = 28;
	
	cout << st.Sheep::m_age << endl;
	cout << st.Tuo::m_age << endl;
}

int main(){
	test1();
	return 0;
}

多态

多态分为两类:

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

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

  • 静态多态的函数地址早绑定--编译阶段确定函数地址

  • 动态多态的函数地址晚绑定--运行阶段确定函数地址

  • 动态多态满足条件

    1. 有继承关系

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

      当发生重写的时候,子类中的虚函数表内部会替换称子类的虚函数地址

      class Animal{
          public:
          virtual void speak(){
              
          }
      };
      
      class Cat : public Animal{
          public:
          virtual void speak(){
              
          }
      };
      
      Animal & animal = cat;
      animal.speak();
      
  • 动态多态使用

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

  • 多态的优点

    1. 代码组织结构清晰
    2. 可读性强
    3. 利于前期和后期的拓展以及维护

在真实开发中,提倡开闭原则:对拓展进行开放,对修改进行关闭

#include<iostream>
#include<string>

using namespace std;

//实现计算器抽象类 
class AbstractCalculator{
	public:
		int n_num1;
		int n_num2;
		
		virtual int getResult(){
			return 0;
		}
		
};

//加法计算器类
class AddCalculator : public AbstractCalculator{
	public:
		int getResult(){
			return n_num1 + n_num2;
		}
}; 


void test1(){
	//多态使用条件
	//父类指针或者引用指向子类对象
	 
	AbstractCalculator *abc = new AddCalculator;
	abc->n_num1 = 10;
	abc->n_num2 = 20;
	cout << abc->getResult() << endl;
    
    //用完后记得销毁
    delete abc;
}

int main(){
	test1();
	
	return 0;
} 

纯虚函数和抽象类

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

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

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

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

抽象类的特点:

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

using namespace std;

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

class Son : public Base{
	public:
		virtual void func(){
			cout << "实现Son的函数" << endl; 
		}
		
};

void test1(){
	//Base b;  错误,抽象类无法实例化对象 
	//new Base;  错误,抽象类无法实例化对象
	
	Son son; //子类必须重写父类的纯虚函数,否则也无法实例化对象 
	son.func();
}


int main(){
	test1();
	return 0;
}

虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到栈区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

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

区别:

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

虚析构语法:

  • virtual ~类名(){}

纯虚析构语法:

  • virtual ~类名() = 0;
class Animal{
    //纯虚析构需要声明也需要实现
    //有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
    virtual ~Animal() = 0;
};
Animal::~Animal(){
    
}

文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放

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

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

文件类型分为两种:

  • 文本文件

    文件以文本的ASCLL码形式存储在计算机中

  • 二进制文件

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

操作文件的三大操作:

  1. ofstream 写操作
  2. ifstream 读操作
  3. fstream 读写操作

文本文件

写操作

写文件步骤如下:

  1. 包含头文件

    #include<iostream>
    
  2. 创建流对象

    ofstream ofs;
    
  3. 打开文件

    ofs.open("文件路径",打开方式);
    
  4. 写数据

    ofs << "写入的数据";
    
  5. 关闭文件

    ofs.close();
    

文件打开方式:

打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在先删除,再创建
ios::binary 二进制方式

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

#include<iostream>
#include<fstream>

using namespace std;

int main(){
	ofstream ofs;
	ofs.open("test.txt",ios::out);
	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl; 
	ofs << "年龄:18" << endl; 
	
	ofs.close();
	
	return 0;
} 

读文件

读文件与写文件的步骤非常相似,但是读取方式相对比较多

读文件步骤:

  1. 包含头文件

    #include<fstream>
    
  2. 创建流对象

    ifstream ifs;
    
  3. 打开文件并判断文件是否打开成功

    ifs.open("文件路径",打开方式);
    
  4. 读数据

    四种读取方式

    #include<iostream>
    #include<fstream>
    #include<string>
    
    using namespace std;
    
    int main(){
    	ifstream ifs;
    	ifs.open("test.txt", ios::in);
    	if(!ifs.is_open()){
    		cout << "文件打开失败" << endl;
    		return 0;
    	}
    	
    	//读数据 
    	//第一种
    	char buf[1024] = {0};
    	//类似于cin >> buf 
    	while(ifs >> buf){
    		cout << buf << endl;
    	}
    	
    	//第二种
    	 char buf[1024] = {0};
    	 //ifs.getline(放入的指针地址, 最多读多少个字节数) 
    	 while(ifs.getline(buf,sizeof(buf))){
    	 	cout << buf << endl;
    	 }
    	 
    	 //第三种
    	 string  buf;
    	 while(getline(ifs, buf)){
    	 	cout << buf << endl;
    	 }
    	 
    	 //第四种
    	 char c;
    	 //EOF end of file 文件尾 
    	 //get 一个一个字节读 不推荐 
    	 while((c = ifs.get()) != EOF){
    	 	cout << c;
    	 }
    	 
    	ifs.close();
    	return 0;
    }
    
  5. 关闭文件

    ifs.close();
    

二进制文件

以二进制的方式对文件进行读写操作

打开方式要指定为ios::binary

写文件

二进制方式写文件主要利用流对象调用成员函数write

函数原型ostream& write(const char*buffer, int len)

参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数

#include<iostream>
#include<fstream>

using namespace std;

class person{
	public:
		char m_name[64];
		int m_age;
};

void test1(){
	ofstream ofs;
	ofs.open("person.txt", ios::out | ios::binary);
	person p = {"张三", 18};
	ofs.write((const char*)&p, sizeof(person));

	ofs.close();
}

int main(){
	test1();
	
	return 0;
}

读文件

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buff, int len)

参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数

#include<iostream>
#include<fstream>

using namespace std;

class person{
	public:
		char m_name[64];
		int m_age;
};

void test1(){
	ifstream ifs;
	
	ifs.open("person.txt", ios::in | ios::binary);
	if(!(ifs.is_open())){
		cout << "文件打开失败" << endl;
		return; 
	}
	
	person p;
	ifs.read((char *)&p, sizeof(person));
	cout << p.m_age << " " << p.m_name << endl;
	
	ifs.close();
}

int main(){
	test1();
	
	return 0;
}
posted @ 2024-04-05 14:35  echory3  阅读(3)  评论(0)    收藏  举报