C++阶段总结第三部分——八千字面向对象总结。三期C++的基础知识完结散花,求点赞求关注求收藏,求一键三连

第三部分 C++核心编程二

本阶段主要针对C++面向对象编写,以及文件操作

目录

第三部分 C++核心编程二

1,面向对象

1.1封装

1.1.1struct和class区别

1.1.2成员属性设置为私有

1.2对象的初始化和清理

1.2.1构造函数和析构函数

1.2.2构造函数的分类及调用

1.2.3拷贝构造函数调用时机

1.2.4构造函数调用规则

1.2.5深拷贝与浅拷贝

1.2.6初始化列表

1.2.7类对象作为类成员

1.2.8静态成员

1.3C++对象模型和this指针

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

1.3.2this指针概念

1.3.3空指针访问成员函数

1.3.4const修饰成员函数

1.4友元函数

1.5运算符重载

1.5.1加号运算符重载

1.5.2运算符重载

1.5.3递增运算符重载

1.5.4赋值运算符重载

1.5.5关系运算符重载

1.5.6函数调用运算符重载

1.6继承

1.6.1 继承方式

1.6.2继承中的对象模型

1.6.3继承中构造和析构顺序

1.6.4继承同名成员处理方式

1.6.5继承同名静态成员处理方式

1.6.6多继承语法

1.6.7菱形继承

1.7多态

1.7.1多态的基本概念

1.7.2 纯虚函数和抽象类

1.7.3虚析构和纯虚析构

2,文件操作

2.1文本文件

2.1.1写文件

2.1.2读文件

2.2二进制文件

2.2.1写文件

2.2.2读文件

C++基础知识完结散花


1,面向对象

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

关于对象的说法:万事万物皆为对象,对象有其属性和行为

eg:

      人可以作为对象,属性有:姓名,身高,体重,年龄

      车也可以作为对象,属性有:轮胎,方向盘,车灯

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

1.1封装

封装意义:

  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制

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

如下:创建一个圆类

#include<iostream>
#include<string>
using namespace std;
#define PI 3.14

class Circle
{
public:
	int R;      //属性

	double Rlong()    //行为
	{
		return 2 * PI*R;
	}
};

int main()
{
	Circle c1;   //实例化,创建一个类
	c1.R = 2;
	cout << "圆的周长为" << c1.Rlong() << endl;

	system("pause");
	return 0;
}

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

三种访问权限:

  1. public 共有权限             类内可以访问,类外可以访问
  2. protected 保护权限       类内可以访问,类外不可以访问(继承时,子类可以访问)
  3. private 私有权限           类内可以访问,类外不可以访问(继承时,子类仍不可访问)

1.1.1struct和class区别

两者的区别在于默认的访问权限不同

区别:

  • struct的默认权限为公共
  • class的默认权限为私有

class Test1
{
    int a;     //默认为私有,不可访问
};

struct Test2
{
    int b;     //默认为公有,可以访问
};

1.1.2成员属性设置为私有

优点:

  • 将所有成员属性设置为私有,可以自己控制读写权限
  • 对于写权限,我们可以检测数据的有效性
#include<iostream>
#include<string>
using namespace std;

class Test1
{
public:
	void set(int n)    //通过函数给私有变量赋值
	{
		a = n;
	}
	int get()       //通过函数获得私有变量的值
	{
		return a;
	}
private:
	int a;
};


int main()
{
	Test1 s1;
	s1.set(10);
	cout << s1.get() << endl;

	system("pause");
	return 0;
}

1.2对象的初始化和清理

1.2.1构造函数和析构函数

对象的初始化和清理十分重要

  1. 一个对象或者变量没有初始状态,对其使用后果是未知的
  2. 同样的使用完一个对象或变量,没有及时的清理,也会造成一定的安全问题

如果我们不提供构造和析构函数,则编译器会提供编译的构造函数和析构函数说空实现

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作

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

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

Test1()
 {
       cout << "Test1的构造函数调用" << endl;
 }

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

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

    ~Test1()
    {
        cout << "Test1的析构函数调用" << endl;
    }

1.2.2构造函数的分类及调用

两种分类方式

1,按参数分类:

  • 有参构造
  • 无参构造

2,按类型分类

  • 普通构造(包括有参构造和无参构造)
  • 拷贝构造

    Test1()        //无参构造
    {
        cout << "Test1的构造函数调用" << endl;
    }
    Test1(int a)   //有参构造
    {
        cout << "Test1的有参构造函数" << endl;
    }
    Test1(const Test1 &p)   //拷贝构造函数
    {
        cout << "Test1的拷贝构造函数" << endl;
    }

三种调用方法

    Test1 p;   //默认构造函数调用

  • 括号法

    Test1 p_1(10);  //有参构造函数
    Test1 p_2(p);   //拷贝构造函数

  • 显示法

    Test1 pp_1 = Test1(10);//有参构造
    Test1 pp_2 = Test1(pp_1);//拷贝构造

  • 隐式转换法

    Test1 ppp_1 = 10;        //相当于  Test1 p_4=Test1(10)
    Test1 ppp_2 = ppp_1;

匿名对象:显示法中的  Test1(10)

这一行执行完,匿名对象就会立即会收掉匿名对象,但是仍然调用了构造函数和析构函数

注意事项:

  1. 调用默认构造函数时,不要加();如Test1(),编译器会认为是一个函数的声明
  2. 不要利用拷贝构造函数,初始化匿名对象

Test1(p);    //编译器会认为是Test1(p)=Test1 p;所以此时对象p就被创建出来了

1.2.3拷贝构造函数调用时机

三种情况:

  • 使用一个已经创建完毕的对象来初始化一个新对象

void test_1()
{
    Test1 p(10);
    Test1 p1(p);
}

  • 值传递的方式给函数参数传值

void do1(Test1 p)
{

}
void test_2()
{
    Test1 p;
}

这个地方可能由于编译器优化,结果会不一样,

推荐一篇我搜了几个小时找到的贺老师的文章:https://helijian.blog.csdn.net/article/details/50977946

  • 以值的方式返回局部对象

Test1 do2()
{
    Test1 p(10);
    return p;
}
void test_3()
{
    Test1 p = do2();
}

1.2.4构造函数调用规则

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

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性的值进行拷贝

调用规则:

  • 如果用户自定义有参构造函数,C++不会提供默认无参构造函数,但是会提供默认拷贝构造函数(需要自定义无参构造)
  • 如果用户自定义拷贝构造函数,C++不会再提供其它构造函数(即需要自定义有参或无参构造函数)

1.2.5深拷贝与浅拷贝

  • 浅拷贝:简单的赋值拷贝操作
  • 深拷贝:在堆区重新申请空间,尽心拷贝操作

主要是当类的属性有指针时,需要用到深拷贝

(仅仅写了一个拷贝构造函数,用于解释深拷贝,其它函数没写)

class Test1{
public:
    Test1(const Test1 &p)
    {
        a = p.a;
        //b = p.b;   编译器浅拷贝的做法,次数运用到指针是错误的
        b = new int(*p.b);
    }
private:
    int a;
    int *b;
};

1.2.6初始化列表

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

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

class Test1{
public:
    Test1(int aa, int bb) :a(aa), b(&bb)
    {

    }
private:
    int a;
    int *b;
};

1.2.7类对象作为类成员

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

class A{};
class B
{
    A a;
};

当创建B对象时,先构造类对象,再构造本身;析构的时候,先析构自己,再析构类对象

1.2.8静态成员

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

静态成员分为:

1,静态成员变量

  • 所有对象共享一份数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化

    static void show()
    {
        a = 20;
        cout << "访问静态成员函数" << endl;
    }

2,静态成员函数

  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量,因为非静态成员变量不能区分出是哪一个对象的成员变量

static int a;

访问方式:

  1. 通过对象访问
  2. 通过类名访问
  3. 类外访问不到静态私有函数

    A b;
    b.show();   //通过对象

    A::show();     //通过类名

#include<iostream>
using namespace std;

class A
{
public:
	static void show()
	{
		a = 20;
		cout << "访问静态成员函数" << endl;
	}

private:
	static void showP()
	{
		a = 20;
		cout << "私有静态成员函数" << endl;
	}
	int b;
	static int a;
};
int A::a = 10;



int main()
{
	A b;
	b.show();   //通过对象

	A::show();     //通过类名

	//A::showP();    //错误,因为不能访问私有静态成员函数

	system("pause");
	return 0;
}

1.3C++对象模型和this指针

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

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

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

  1. 空对象占用的内存空间为:0;为了区分空对象所占位置,每个空对象都有一个独一无二地址
  2. 当类中有非静态成员变量时,大小即为非静态成员变量的大小;向类中添加静态成员变量或函数,大小都不变

1.3.2this指针概念

this指针指向被调用的成员函数所属的对象

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

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

this指针的用途:

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

this指针的本质是指针常量,指针的指向是不可以修改的

A *const this

#include<iostream>
using namespace std;

class A
{
public:
	A(int a)
	{
		this->a = a;
	}

	A& add(A &p)       //如果此处不是用引用返回,则每次返回都会创建一个新的对象
	{
		this->a += p.a;
		return *this;
	}

	int a;
};



int main()
{
	A m(10);
	A n(10);
	n.add(m).add(m).add(m);
	cout << n.a << endl;

	system("pause");
	return 0;
}

1.3.3空指针访问成员函数

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

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

即:调用的成员函数不能包含属性等东西,因为this为空,无法显示

#include<iostream>
using namespace std;

class A
{
public:
	void show()
	{
		cout << "打印***" << endl;
	}

	void put()
	{
		//cout << a << endl;    //错误;此处a相当于this->a,this为空,所以无法输出;
	}
private:
	int a;
};

void test()
{
	A *a = NULL;
	a->show();
	a->put();
}

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

1.3.4const修饰成员函数

常函数:

  • 成员函数后加const后我们称为这个函数为常函数  (void change_2() const)
  • 常函数内不可修改成员属性
  • 成员函数声明时加关键字mutable后,在常函数中依然可以修改(mutable int b;)

常对象:

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

class A
{
public:
	void change_1()
	{
		a = 100;
		b = 100;
	}

	//在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改
	void change_2() const      //相当于 const A* const this
	{
		//a = 100;     //不可以修改
		b = 100;    //特殊变量,可以修改
	}

	int a;
	mutable int b;    //特殊变量,即使在常函数中也可以修改
};

void test()
{
	A p;
	p.a = 100;
	p.b = 100;
	p.change_1();
	p.change_2();
}

void test2()
{
	const A q;
	//q.a = 100;     //常对象不能修改普通值
	q.b = 100;
	//q.change_1();     //错误,常对象只能调用常函数,因为普通成员函数可以修改属性
	q.change_2();
}

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

1.4友元函数

友元的关键字:friend

三种实现形式:

  • 全局函数做友元

即:当全局函数中的对象想要访问类中的私有属性时,需要将该全区函数在类中加以声明为friend类型

class Student
{
    //此时全局函数show是类Student的好朋友,可以访问类的私有成员
    friend void show(Student &a);          //此时全局函数中的对象都可以访问类中的私有属性
........

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

class Student
{
	friend void show(Student &a);

public:
	Student()
	{
		age = 18;
		name = "小明";
	}

	string name;

private:
	int age;
};

void show(Student &a)
{
	cout << a.name << endl;
	cout << a.age << endl;
}

int main()
{
	Student m;
	show(m);
	system("pause");
	return 0;
}
  • 类做友元

class Class
{
    ..........
};

class Student
{
    friend class Class;
.........

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

class Student;

class Class
{
public:
	Class();
	void show();
private:
	Student *s;
};

class Student
{
	friend class Class;
public:
	Student()
	{
		age = 18;
		name = "小明";
	}
	string name;
private:
	int age;
};
Class::Class()
{
	s = new Student;
}

void Class::show()
{
	cout << s->age << endl;
	cout << s->name << endl;
}
int main()
{
	Class a;
	a.show();
	system("pause");
	return 0;
  • 成员函数做友元

class Class
{
    ..........
};

class Student
{
       friend void Class::show();        //和类做友元类似,此处把类变成了函数
.........

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

class Student;

class Class
{
public:
	Class();
	void show();
	void show1();
private:
	Student *s;
};

class Student
{
	friend void Class::show();
public:
	Student()
	{
		age = 18;
		name = "小明";
	}
	string name;
private:
	int age;
};
Class::Class()
{
	s = new Student;
}

void Class::show()
{
	cout << s->age << endl;        //类的友元函数,可以访问私有属性
	cout << s->name << endl;
}
void Class::show1()
{
//	cout << s->age << endl;       //不能访问私有属性
	cout << s->name << endl;
}
int main()
{
	Class a;
	a.show();
	a.show1();
	system("pause");
	return 0;
}

1.5运算符重载

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

1.5.1加号运算符重载

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

和正常的相加思路类似,只不过这里需要引用其他的对象作为参数;

    //成员函数实现“+”运算符重载              p1=p2+p3;类似于:p1=p2.operator+(p3);
    Class operator+(const Class &p){
        Class q(0, 0);
        q.a = this->a + p.a;
        q.b = this->b + p.b;
        return q;
    }

//全局函数实现“+”运算符重载             p1=p2+p3;类似于:p1=operator+(p2,p3);
Class operator+(const Class &p1, const Class &p2){
    Class q(0,0);
    q.a = p1.a + p2.a;
    q.b = p1.b + p2.b;
    return q;
}

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

class Class
{
public:
	Class(int a, int b) :a(a), b(b){}
	void show()
	{
		cout<<"a="<< a << "    b=" << b << endl;
	}

	//成员函数实现“+”运算符重载
	Class operator+(const Class &p){
		Class q(0, 0);
		q.a = this->a + p.a;
		q.b = this->b + p.b;
		return q;
	}
//private:
	int a;
	int b;
};

////全局函数实现“+”运算符重载
//Class operator+(const Class &p1, const Class &p2){
//	Class q(0,0);
//	q.a = p1.a + p2.a;
//	q.b = p1.b + p2.b;
//	return q;
//}

int main()
{
	Class a(10,10);
	Class b(20, 30);
	Class c(0, 0);
	c= a + b;
	c.show();
	system("pause");
	return 0;
}

1.5.2运算符重载

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

//只能用全局函数实现“<<”运算符重载
ostream & operator<<(ostream &cout, const Class &p)    //本质operator<<(cout,p),简化cout<<p
{
    cout << p.a << "    " << p.b << endl;
    return cout;             //此处返回cout的为了能够实现链式编程,可以多次返回输出
}

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

class Class
{
public:
	Class(int a, int b) :a(a), b(b){}
	

	int a;
	int b;
};

//如果利用成员函数重载 左移运算符 p.operator<<(cout)   简化版本 p<<cout;p在左侧错误

//只能用全局函数实现“<<”运算符重载
ostream & operator<<(ostream &cout, const Class &p)    //本质operator<<(cout,p),简化cout<<p
{
	cout << p.a << "    " << p.b << endl;
	return cout;
}

int main()
{
	Class a(10,10);
	cout << a<<endl;
	system("pause");
	return 0;
}

1.5.3递增运算符重载

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

    //重载前置++运算符
    Class &operator++()           //返回引用是为了一直对一个数据进行增值操作
    {
        a++;
        return *this;
    }

    //重载后置++运算符          //此时不能再使用链式操作了
    Class operator++(int)      //此处int作为占位符,用来区分前置还是后置
    {
        Class p = *this;    //记录当前本身的值,然后让本身的值加1,但是返回的值还是以前的值
        a++;
        return p;
    }

前置返回的是一个引用,而后置返回的是结果是一个临时对象。因为后置的时候原来的对象已经被++改变了,所以需要一个新对象来保存改变之前的值。而前置用引用则是为了不产生临时变量,减少内存的消耗而已。

所以前置引用可以使用链式的方法,多次前置++,而后置不可以多次++;

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

class Class
{
	friend ostream & operator<<(ostream &cout, const Class &p);

public:
	Class(int a) :a(a){}
	
	//重载前置++运算符
	Class &operator++()           //返回引用是为了一直对一个数据进行增值操作
	{
		a++;
		return *this;
	}

	//重载后置++运算符
	Class operator++(int)      //此处int作为占位符,用来区分前置还是后置
	{
		Class p = *this;    //记录当前本身的值,然后让本身的值加1,但是返回的值还是以前的值
		a++;
		return p;
	}
private:
	int a;
};


ostream & operator<<(ostream &cout, const Class &p) 
{
	cout << p.a << endl;
	return cout;
}

int main()
{
	Class a(10);
	cout << ++(++a)<<endl;
	cout << a << endl;
	cout << a++ << endl;
	cout << a << endl;
	system("pause");
	return 0;
}

1.5.4赋值运算符重载

赋值运算符需要注意的地方就是当数据在堆区存储时,注意开辟新的空间以及及时释放空间

    void operator = (Class &p)
    {
        if (a != NULL)          //如果已经有值,需要先释放,再进行赋值
        {
            delete a;
            a = NULL;
        }
        //深拷贝
        a = new int(*p.a);
    }

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

class Class
{
	friend ostream & operator<<(ostream &cout, const Class &p);

public:
	Class(int a){
		this->a = new int(a);
	}
	
	void operator = (Class &p)
	{
		if (a != NULL)
		{
			delete a;
			a = NULL;
		}
		//深拷贝
		a = new int(*p.a);
	}
private:
	int *a;
};


ostream & operator<<(ostream &cout, const Class &p) 
{
	cout << *p.a << endl;
	return cout;
}

int main()
{
	Class a(10);
	Class b(20);
	a = b;
	cout << a << endl;
	system("pause");
	return 0;
}

1.5.5关系运算符重载

作用:可以让两个自定义类型对象进行对比操作

    bool operator == (Class &p)             //相对比较简单,就简单传值,然后对比一下
    {
        if (this->a == a) {
            return true;
        }
        else {
            return false;
   } }

#include<iostream>
using namespace std;

class Class
{

public:
	Class(int a){
		this->a = a;
	}
	
	bool operator == (Class &p)
	{
		if (this->a == a)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
private:
	int a;
};

int main()
{
	Class a(10);
	Class b(20);
	if (a == b)
	{
		cout << "两个数相等" << endl;
	}
	else
	{
		cout << "两个数不相等" << endl;
	}
	system("pause");
	return 0;
}

1.5.6函数调用运算符重载

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

    void operator()(string world)
    {
        cout << world << endl;
    }

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

class Class
{
public:
	void operator()(string world)
	{
		cout << world << endl;
	}
};

int main()
{
	Class a;
	a("hells world");     //由于使用起来类似于函数调用,因此称为仿函数;非常灵活,没有固定写法
	
	//此处为匿名函数对象
	Class()("hello world");          //Class()代替了类似于a
	system("pause");
	return 0;
}

1.6继承

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

继承语法:class A(子类) : 继承方式  B(父类)

子类也称为派生类;父类也称为基类

派生类中的成员,包含两大部分:

  • 从基类继承过来的,表现其共性
  • 自己增加的函数,体现其个性

1.6.1 继承方式

一共有三种:

  • 公有继承     (三种权限不变)
  • 保护继承     (公+保->保,私不变)
  • 私有继承     (公+保->私,私不变)
  • 继承时,私有成员子类均不可访问,保护成员子类可以访问

下面这段代码,详细注释了各种情况下的访问和继承

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

class Class
{
public:
	int a;
protected:
	int b;
private:
	int c;
};

class sun1:public Class
{
	void fun()
	{
		a = 10;    //公有 不变
		b = 10;    //保护 不变
		//c = 10;  错误,子类不可访问父类的私有成员
	}
};
class sun2 :protected Class
{
	void fun()
	{
		a = 10;     //保护 变为
		b = 10;     //保护 不变
		//c = 10;   错误,子类不可访问父类的私有成员
	}
};
class sun3 :private Class
{
	void fun()
	{
		a = 10;     //私有 变为
		b = 10;     //私有 变为
		//c = 10;   错误,子类不可访问父类的私有成员
	}
};

void test()
{
	sun1 p1;
	p1.a = 10;
	//p1.b = 20;     //保护b不可访问
	//p1.c = 30;     //私有c不可访问

	sun2 p2;
	//p2.a = 10;      //保护a不可访问
	//p2.b = 20;      //保护b不可访问
	//p2.c = 30;      //私有c不可访问

	sun3 p3;
	//p3.a = 10;      //私有a不可访问
	//p3.b = 20;      //私有b不可访问
	//p3.c = 30;      //私有c不可访问

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

1.6.2继承中的对象模型

继承规则:

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

class Class{
public:   int a;
protected:  int b;
private:   int c;
};
class sun1 :public Class{
    int a;
};                        //此时sun1定义出的对象大小就为16,继承的三个加上新增加的一个

1.6.3继承中构造和析构顺序

先构造父类,再构造子类,析构的顺序与构造的顺序相反(可以自己写代码实验)

1.6.4继承同名成员处理方式

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

    sun p;
    cout << p.a << endl;
    cout << p.Base::a << endl;

  • 当子类与父类拥有同名成员函数,子类会隐藏父类中所有版本的同名成员函数
  • 如果想访问父类中隐藏的同名成员函数,需要加父类的作用域

    p.change();    //子类中的成员函数
    //p.change(a);    //此处虽然是重载函数,但是只要子类父类发生重名,子类就会隐藏父类所有同名函数
    p.Base::change(9);     //父类中的带参成员函数
    p.Base::change();      //父类中的不带参成员函数

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

class Base{
public: 
	Base()
	{
		a = 10;
	}
	void change()
	{
		a = 100;
	}
	void change(int x)
	{
		a = x;
	}
	int a;
};
class sun:public Base{
public:
	sun()
	{
		a = 20;
	}
	void change()
	{
		a = 200;
	}
	int a;
};

void test()
{
	sun p;
	//成员
	cout << p.a << endl;
	cout << p.Base::a << endl;

	//成员函数
	p.change();    //子类中的成员函数
	//p.change(a);    //此处虽然是重载函数,但是只要子类父类发生重名,子类就会隐藏父类所有同名函数
	p.Base::change(9);     //父类中的带参成员函数
	p.Base::change();      //父类中的不带参成员函数
}
int main()
{
	test();
	system("pause");
	return 0;
}

1.6.5继承同名静态成员处理方式

静态成员和非静态成员出现同名,处理方式一致,只不过有两种访问方式

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

    cout << p.a << endl;
    cout << p.Base::a << endl;

    cout << sun::a << endl;
    cout << sun::Base::a << endl;

1.6.6多继承语法

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

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

(实际开发中不建议)

class Base :public Base1, public Base2{}

当父类中出现同名成员,需要加作用域进行区分

    cout << p.a << endl;
    cout << p.Base1::a << endl;
    cout << p.Base2::a << endl;

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

class Base1{
public:
	Base1()
	{
		a = 10;
	}
	int a;
};

class Base2{
public:
	Base2()
	{
		a = 20;
	}
	int a;
};

class Base :public Base1, public Base2
{
public:
	Base()
	{
		a = 30;
	}

	int a;
};

void test()
{
	Base p;
	cout << p.a << endl;
	cout << p.Base1::a << endl;
	cout << p.Base2::a << endl;
}

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

1.6.7菱形继承

概念:

  1. 两个派生类继承同一个基类
  2. 又有某个类同时继承两个派生类
  3. 这种继承称为菱形继承

如图所示:B1,B2继承A,C又继承B1,B2。

当出现这种零星继承时,如果两个父类拥有相同数据,需要加以作用域区分

这份数据我们知道,只需要一份就足够了,而菱形继承导致数据有两份,资源浪费。

解决方法:

  • 利用虚继承 解决菱形继承的问题
  • 继承之前加上关键字virtual变为虚继承
  • A变为虚基类

class B1 :virtual public A{};
class B2 :virtual public A{};

1.7多态

1.7.1多态的基本概念

多态分为两类:

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

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

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

地址早绑定的话,在编译阶段就确定了函数地址,因此函数就不会更改了,一直显示某一个

若要按要求执行,使得函数不提前绑定,就需要用到动态多态,即加上关键字"virtual",变成虚函数

动态多态满足的条件:

  1. 有继承关系
  2. 子类重写父类的虚函数          (重写:函数返回值类型,函数名,参数列表完全一致称为重写)

动态多态的使用:

  1. 父类指针或者引用,执行子类对象
#include<iostream>
using namespace std;
#include<string>

class Base1{
public:
    //虚函数
	virtual void show()
	{
		cout << "Base1" << endl;
	}
};

class Base2:public Base1
{
public:
	void show()
	{
		cout << "Base2" << endl;
	}
};

class Base3 :public Base1
{
public:
	void show()
	{
		cout << "Base3" << endl;
	}
};

void test(Base1 &p)
{
	p.show();
}

int main()
{
	Base1 p1;
	Base2 p2;
	Base3 p3;
	test(p1);
	test(p2);
	test(p3);
	system("pause");
	return 0;
}

1.7.2 纯虚函数和抽象类

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

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

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

抽象类特点:

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

class Base1{
public:
    //纯虚函数,所以此类也被称为抽象类
    virtual void show() = 0;
};

则不能Base1 p; 因为Base1是抽象类,抽象类不能实例化对象。

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

class Base1{
public:
	//纯虚函数,所以此类也被称为抽象类
	virtual void show() = 0;
};

class Base2:public Base1
{
public:
	//重写父类Base1中的show函数
	void show(){ cout << "Base2"; }
};

class Base3 :public Base1
{

};
void test()
{
	//Base1 p;         //抽象类不能实例化对象
	//Base3 p;       //如果不重写抽象类中的纯虚函数,则子类也变成抽象类
	Base2 p;
	p.show();
}

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

1.7.3虚析构和纯虚析构

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

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

  • 如果子类中没有堆区数据,可以不写虚析构或者纯虚函数

虚析类和纯虚析构共性:

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

虚析类和纯虚析构区别:

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

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名()=0

    //利用虚析构可以解决父类释放子类对象时堆区数据泄露的问题
    virtual ~Base1()
    {
       cout << "Base1的析构函数" << endl;
    }

    //纯虚析构  需要声明也需要实现
    //有了纯虚析构,这个类也属于抽象类,无法实例化对象
    virtual ~Base1() = 0;

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

class Base1{
public:
	Base1()
	{
		cout << "Base1的构造函数" << endl;
	}

	////利用虚析构可以解决父类释放子类对象时堆区数据泄露的问题
	//virtual ~Base1()
	//{
	//	cout << "Base1的析构函数" << endl;
	//}

	//纯虚析构  需要声明也需要实现
	//有了纯虚析构,这个类也属于抽象类,无法实例化对象
	virtual ~Base1() = 0;
};
Base1::~Base1()
{
	cout << "Base1纯虚析构函数" << endl;
}

class Base2:public Base1
{
public:
	Base2()
	{
		cout << "Base2的构造函数" << endl;
	}
	~Base2()
	{
		cout << "Base2的析构函数" << endl;
	}
};

void test()
{
	Base1 *p = new Base2;
	//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区数据,会出现内存泄露
	delete p;
}

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

2,文件操作

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

通过文件可以将数据持久化,需要包含的头文件<fstream>

文件类型分为两种:

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

操作文件的三大类

  • ofstream:写操作
  • ifstream:读操作
  • fstream:读写操作

2.1文本文件

2.1.1写文件

步骤:

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

      //头文件
    ofstream test;
    test.open("test.txt", ios::out);
    test << "姓名:小明" << endl;
    test.close();

文件打开方式

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

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

2.1.2读文件

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

读取文件步骤:

  1. 包含头文件:#include<fstream>
  2. 创建流对象:ifstream test;
  3. 打开文件:test.open("文件路径",打开方式);
  4. 读数据:四种方式读取
  5. 关闭文件:test.close();
#include<iostream>
using namespace std;
#include<fstream>
#include<string>

int main()
{
	ifstream test;
	test.open("test.txt", ios::in);

	////第一种
	//char buf_1[1024] = { 0 };
	//while (test >> buf_1)
	//{
	//	cout << buf_1 << endl;
	//}

	////第二种
	//char buf_2[1024] = { 0 };
	//while (test.getline(buf_2,sizeof(buf_2)))
	//{
	//	cout << buf_2 << endl;
	//}

	////第三种
	//string buf_3;
	//while (getline(test,buf_3))
	//{
	//	cout << buf_3 << endl;
	//}

	////第四种
	//char buf_4;
	//while ((buf_4 = test.get()) != EOF)
	//{
	//	cout << buf_4;
	//}

	test.close();
	system("pause");
	return 0;
}

2.2二进制文件

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

打开方式指定为 ios::binary

2.2.1写文件

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

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

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

2.2.2读文件

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

函数原型:istream& read(const char *buffer,int len);

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

关于文件操作链接:https://blog.csdn.net/qq_46423166/article/details/106963861

C++基础知识完结散花

猜你喜欢

转载自blog.csdn.net/qq_46423166/article/details/107116436