c++知识点复习

本文介绍了C++中的面向对象编程概念,包括类的封装、继承、多态(虚函数)等。详细讨论了构造函数、析构函数、拷贝构造函数的使用,以及虚继承和虚函数在多态中的作用。此外,还提到了抽象类和纯虚函数的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

c++类的封装

类中的成员权限

 类的继承与派生

多态和虚函数(重点) 


c++类的封装

面向对象编程的三大基本思想: 类的封装,继承,多态。

1.类
                             生活中的分类:   动物  --》哺乳动物
                                                                      两栖动物
                                                                      陆生动物
                                                       动物的行为: 吃  跑  睡
                                                       动物的属性:皮毛颜色   腿的数量   牙齿数量   
                            C++把我们生活中的分类思想做了抽象,把所有的事物都抽象为类,类中就包含行为和属性
                                                       如何表示行为 --》函数
                                                       如何表示属性 --》变量
      2.语法规则
                       class  类的名字   //类的名字一般建议你首字符大写
                       {
                                类中的成员
                       };
       类中的成员
                   总共有两种类成员:
                                成员方法(成员函数):用来描述你这个类具备的行为特征
                                成员属性(成员变量):  用来描述你这个类具备的属性参数
       类的使用
                 类的使用跟我们平时使用基本类型的变量是一样的
                 对象:就是你定义的一个变量,对象是类的一个实例,定义一个对象就相当于你定义了这个类的一个变量

                 比如:  int  a;      //我定义了int类型的变量
                             float b;   //我定义了float类型的变量
                             struct student stu;   //我定义了struct student类型的变量
                 同样的道理:
                            类名   对象名
                             Cat      c1;   //我定义了Cat类型的变量(C++称之为对象)
       
       通过对象去调用类的成员
                 总共两种写法:
                            写法一:使用栈空间
                                             Cat c1;    //c1使用栈空间
                                             c1.sleep();
                            写法二:使用堆空间
                                            Cat *c2=new Cat;
                                            c2->sleep();
                                            delete c2;
 

类中的成员权限

1.什么是类中的成员权限
               指的就是C++允许程序员对类中的成员方法以及成员变量设置不同的访问权限,目的是为了保证数据的安全,防止数据泄露
   
   2.三种权限
                  权限修饰符:   public     private    protected
                  public  --》公有     类中的成员既可以在类的内部使用,也可以在类的外部通过对象调用
                  private --》私有    类中的成员既可以只能在类的内部使用,不能在类的外面使用(对外隐藏了数据,起到保护数据的作用)
                  protected --》保护   
     注意:定义类的时候没有使用任何权限修饰符,默认是私有的
              一般实际开发中,把成员函数设置成公有的,把成员变量设置为私有的

成员函数的两种写法
=================================
   1.成员函数的两种写法
                  第一种:成员函数的定义放在类的里面
                                 代码量比较小,直接写在类的里面
                  第二种:成员函数的定义放在类的外面
                                代码量比较大,写在类的外面
                                int  Cat::sleep()   //  ::叫做类作用域运算符,表示sleep这个函数属于Cat类的成员函数
                                {

                               }

类的大小

 1.规则
                类的大小跟类中所有成员变量的大小有关,跟成员函数没有任何关系
               大小计算规则跟结构体类似
 
    2.复习结构体求大小
             什么叫8字节对齐: 变量大小除以8,看看能不能整除,不能整除填充垃圾数凑够8字节整除
             第一步:找出结构体或者类中最大类型的成员
             第二步:如果类型最大的成员,类型>=8字节  --》整个结构体/类按照8字节对齐
                          如果类型最大的成员,类型<8字节  --》整个结构体/类按照最大成员类型大小对齐  
            struct  xx
           {
                 int age;  //4字节 --》4不能整除8,空出4字节,凑够8字节整除
     char *p;  //8字节  --》能整除8
     double weight;  //8字节  --》能整除8
          }
           第一步:char *和double都是8字节,也是最大的类型
           第二步:确定整个结构体按照8字节对齐
           第三步:按照8字节对齐的标准去计算结构体的大小 

this指针

1.引出this指针
              this指针:专门指向当前对象的一个指针
             当前对象:谁调用了成员函数,当前对象就是谁 

#include <iostream>

using namespace std;

class Cat
{
public:
	//设置猫的年龄
	void setAge(int newage)
	{
		cout<<"研究this究竟是不是指向当前对象: "<<this<<endl;
		age=newage;
	}
	void showAge()
	{
		cout<<"研究this究竟是不是指向当前对象: "<<this<<endl;
		cout<<age<<endl;
	}
	//比较两只猫的年龄,返回年龄较大的那只猫
	Cat &compareAge(Cat &other)
	{
		//Cat *this=当前对象的地址
		cout<<"研究this究竟是不是指向当前对象: "<<this<<endl;
		if(this->age<other.age)
			return other;
		else 
			return *this; 
	}
private:
	int age;
};
int main()
{
	//定义两个猫的对象
	Cat c1;
	Cat c2;
	c1.setAge(5);
	c2.setAge(7);
	cout<<"c1地址: "<<&c1<<endl;
	cout<<"c2地址: "<<&c2<<endl;
	//比较两只猫的年龄
	Cat temp=c1.compareAge(c2);      //此时temp是个独立的对象
	//Cat &temp=c1.compareAge(c2);   //此时temp是个引用
	temp.showAge();
	cout<<"较大的那只猫地址是: "<<&temp<<endl;
	
	c2.compareAge(c1);
}

c++中的字符串string(本质上也是个类)

class string   //表示字符串
    {
          提供了大量的成员方法供我们程序员调用,实现字符串的基本操作非常方便
   }
   www.cplusplus.com   //C++在线的API(C++中类和成员方法的介绍)文档
   1.C++存储字符串
       写法一:用数组
           char buf[10];
       写法二:用指针
           char *p=new char[10];
           char *q=malloc(10);
       写法三:用string
       写法四:用容器

   2.常用的方法
          (1)把string转换成char *
                        const char *p=str3.c_str();
            str2.copy(p,2);    //报错,p是const的修饰的指针,copy要求不能用const修饰

#include <iostream>
using namespace std;

int main()
{
	//定义两个字符串对象
	string str1="hello";
	string str2="world";
	
	//字符串的拼接
	//C语言的做法: sprintf()  strcat()
	string str3=str1+str2;
	cout<<str3<<endl;
	
	//字符串比较
	//C语言的做法:strcmp()
	if(str1==str2)
		cout<<"相等"<<endl;
	else
		cout<<"不相等"<<endl;
	
	//字符串的赋值
	//C语言的做法:strcpy()
	str2="我爱中国";
	cout<<str2<<endl;
	
	//不用考虑数组/指针越界
	string str4="fhshfhsdfhsdhfhdshfhdsjokihfhdsujfghdsgjkdfhgjfdhgjdfhgdfmjghdfjghfdjghdfjghfr";
	
	//追加字符串
	//str4.append(str1);
	str4.append(str1,2,3);
	cout<<"str4 is: "<<str4<<endl;
	
	
}

 auto关键字(自动匹配数据类型)


   1.作用
            自动匹配类型
  
   2.C++标准
            C++98  
            C++03
            C++11 --》auto关键字
           g++ string练习题.cpp   -std=c++11   //要求编译器依照C++11标准去编译代码

#include <iostream>

using namespace std;

class Animal
{
	
};
int main()
{
	int a=99;
	auto b=a;
	
	Animal a1;
	auto a2=a1;
}

 构造函数(构造器)

1.作用
            当我们创建一个对象的时候,就会自动调用构造函数
            构造函数专门用于新建对象,在新建对象的时候我们可以通过构造函数去初始化对象中的成员变量

   2.语法规则和特点
            语法规则:
                     类名(形参)
                     {

                    }
           特点:
                     第一:构造函数的名字必须跟类名相同
                     第二:构造函数没有返回值类型
                     第三:构造函数可以重载,目的为了实现创建对象的多样化
                     第四:如果程序员没有写任何构造函数,系统默认会自动生成一个无参构造函数,这个默认的无参构造函数代码中啥都没有做
                               如果程序员有定义构造函数(带参数或者不带参数都可以),系统就不会再去生成默认的无参构造
                                          Cat()   //系统提供的默认无参构造函数
                                          {
                                                    什么代码都没有
                                         }
                     

 3.构造函数的几种写法
                写法一:无参构造函数 Cat()
                写法二:带参数的构造函数  Cat(形参.............)
                写法三:带默认参数的构造函数  
                写法四:参数列表形式的构造函数
                写法五:指定调用父类的构造函数

   4.构造函数的调用
                Cat c1;
                Cat *c2=new Cat(5);
                Cat c3={5,5.5};     //C++11新特性,列表形式初始化对象
                Cat c4(4,3.5);
                Cat c5=5;            //当构造函数只有一个参数的时候,可以写成这样

#include <iostream>

using namespace std;

class Cat
{
public:
	//构造函数
	
/*  写法一:无参构造
    Cat()  //无参构造
	{
		age=0;
		weight=0.0;
		cout<<"猫的没有构造函数被调用了"<<endl;
	} 
	写法二:带参数的构造函数
	Cat(int _age,float _weight)
	{
		age=_age;
		weight=_weight;
		cout<<"带参数的构造函数被调用了"<<endl;
	}  */
	
    //写法三:带默认参数的构造函数--》注意会跟其他版本的构造函数冲突
/* 	Cat(int _age=0,float _weight=0.0)
	{
		age=_age;
		weight=_weight;
		cout<<"带默认参数的构造函数被调用了"<<endl;
	}  */
	
	//写法四:参数列表形式的构造函数
	Cat(int _age,float _weight):age(_age),weight(_weight)
	{
		cout<<"参数列表形式的构造函数被调用了"<<endl;
	}
	
	
	void show()
	{
		cout<<"我是一只猫,年龄 "<<age<<"  体重 "<<weight<<endl;
	}
	
private:
	int age;
	float weight;
};


int main()
{
	//创建三只猫对象
	//Cat c1;
	Cat c2(5,10.5);
	Cat c3(3,6.5);
	//c1.show();
	c2.show();
	c3.show();
	
	
}

 构造函数的调用

#include <iostream>

using namespace std;

class Cat
{
public:
	//构造函数
    Cat()  //无参构造
	{
		age=0;
		weight=0.0;
		cout<<"猫的没有构造函数被调用了"<<endl;
	} 

	Cat(int _age,float _weight)
	{
		age=_age;
		weight=_weight;
		cout<<"带两个参数的构造函数被调用了"<<endl;
	}  
	
	Cat(int _age)
	{
		age=_age;
		cout<<"带一个参数的构造函数被调用了"<<endl;
	}  
	
private:
	int age;
	float weight;
};


int main()
{
	Cat c1;   
	Cat *c2=new Cat(5);
	Cat c3={5,5.5};  //C++11新特性,列表形式初始化对象
	Cat c4(4,3.5);
	Cat c5=5;  //当构造函数只有一个参数的时候,可以写成这样
}

析构函数(析构器)

1.作用
            当对象释放的时候,就会自动调用析构函数
                    对象的释放无非就两种情况:
                              情况一:栈空间上的对象  Cat c1;                      //自动释放
                              情况二:堆空间上的对象  Cat *c2=new Cat();  //主动调用delete释放
            析构函数专门用于释放对象中申请的堆空间,或者关闭一些文件,硬件设备,做一个收尾工作

   2.语法规则和特点
            语法规则:
                     ~类名()
                     {

                    }
           特点:
                     第一:析构函数的名字必须跟类名相同,前面要加上~
                     第二:析构函数没有返回值类型
                     第三:析构函数不可以重载
                     第四:如果程序员没有写任何析构函数,系统默认会自动生成一个析构函数,这个默认的析构函数代码中啥都没有做
                               如果程序员有定义析构函数,系统就不会再去生成默认的析构函数
                                          ~Cat()   //系统提供的默认析构函数
                                          {
                                                    什么代码都没有
                                         }

#include <iostream>

using namespace std;
class Cat
{
public:
	Cat()
	{
		cout<<"猫构造了"<<endl;
		//open(某个文件/硬件设备的驱动)
		//name=new char[10];
	}
	
	//定义析构函数
	~Cat()
	{
		cout<<"猫析构了"<<endl;
		//close(某个文件/硬件设备的驱动)
		//delete []name;
	}
private:
	char *name;
};

int main()
{
	//定义猫的对象--》栈空间
	Cat c1;
	
	//定义猫的对象--》堆空间
	Cat *c2=new Cat;
	//Cat *c3=new Cat();
	delete c2;
}

拷贝构造函数

  1.作用
            当你用一个对象去初始化赋值给另外一个对象的时候,会自动调用拷贝构造函数
                    Cat c1;    //已经定义好了c1,c1调用的是构造函数
                    Cat c2=c1;   //c2调用的是拷贝构造函数

   2.语法规则和特点
           类名(类对象的引用)
           {
                  代码;
          }
     特点
                     第一:拷贝构造函数的名字必须跟类名相同,参数是类对象的引用
                     第二:如果程序员没有写任何拷贝构造函数,系统默认会自动生成一个拷贝构造函数,这个默认的拷贝构造函数代码中会把c1中所有成员变量的值赋值给c2
                               如果程序员有定义拷贝构造函数,系统就不会再去生成默认的拷贝构造函数
                               默认的拷贝构造函数完成了如下代码:
                                       Cat(Cat &other)
                                      {
                                            this->age=other.age;
                                            this->weight=other.weight;
                                     }

   3.总结拷贝构造函数会被调用的三种情况
             第一种:用一个对象去初始化赋值给另外一个对象的时候
             第二种:函数的形参是类的对象,传递实参的时候
             第三种:函数的返回值是类的对象

#include <iostream>

/*
	用一个对象去初始化赋值给另外一个对象的时候--》会调用拷贝构造函数
*/
using namespace std;

class Cat
{
public:
	//构造函数
	Cat()
	{
		cout<<"没有参数的猫构造了"<<endl;
		//初始化一下私有成员
		age=0;
		weight=0.0;
	}
	
	Cat(int _age,float _weight)
	{
		cout<<"带两个参数的构造"<<endl;
		age=_age;
		weight=_weight;
	}
	
	//定义析构函数
	~Cat()
	{
		cout<<"猫析构了"<<endl;
	}
	
	//自定义一个拷贝构造函数--》目的为了看清楚拷贝构造函数是否调用了
	Cat(Cat &other)
	{
		cout<<"自定义的拷贝构造函数调用了"<<endl;
	}
	
	void show()
	{
		cout<<"年龄: "<<age<<" 体重: "<<weight<<endl;
	}

private:
	int age;
	float weight;
};


int main()
{
	//定义猫的对象
	Cat c1;
	
	//一个对象初始化赋值给另外一个新的对象
	//写法一
	Cat c2=c1;
	cout<<"111"<<endl;
	//写法二
	Cat c3(c1);
	
	cout<<"222"<<endl;
	//写法三
	Cat *c4=new Cat(c1);   //笔试题很狡猾的地方
	cout<<"333"<<endl;
	Cat *c5=new Cat;       //笔试题很狡猾的地方
	
	
}

#include <iostream>

/*
	函数的形参是类的对象,传递实参的时候--》会调用拷贝构造函数
*/
using namespace std;

class Cat
{
public:
	//构造函数
	Cat()
	{
		cout<<"没有参数的猫构造了"<<endl;
		//初始化一下私有成员
		age=0;
		weight=0.0;
	}
	
	Cat(int _age,float _weight)
	{
		cout<<"带两个参数的构造"<<endl;
		age=_age;
		weight=_weight;
	}
	
	//定义析构函数
	~Cat()
	{
		cout<<"猫析构了"<<endl;
	}
	
	//自定义一个拷贝构造函数--》目的为了看清楚拷贝构造函数是否调用了
	Cat(Cat &other)
	{
		cout<<"自定义的拷贝构造函数调用了"<<endl;
	}
	
	void show()
	{
		cout<<"年龄: "<<age<<" 体重: "<<weight<<endl;
	}

private:
	int age;
	float weight;
};

//定义一个函数,把猫的对象作为形参
int fun(Cat c)  //Cat c=c1
{
	cout<<"彭老师定义的函数fun调用了"<<endl;
}

int otherfun(Cat &c)  //Cat &c=c1;
{
	cout<<"彭老师定义的函数otherfun调用了"<<endl;
}
int main()
{
	//定义猫的对象
	Cat c1;
	
	//调用自定义的函数fun
	fun(c1);  //对象传值--》调用拷贝构造函数
	
	//调用自定义的函数otherfun
	otherfun(c1);  //对象传引用/传地址--》不会调用拷贝构造函数
	
}

#include <iostream>

/*
	函数的返回值是类的对象--》会调用拷贝构造函数
*/
using namespace std;

class Cat
{
public:
	//构造函数
	Cat()
	{
		cout<<"没有参数的猫构造了"<<endl;
		//初始化一下私有成员
		age=0;
		weight=0.0;
	}
	
	Cat(int _age,float _weight)
	{
		cout<<"带两个参数的构造"<<endl;
		age=_age;
		weight=_weight;
	}
	
	//定义析构函数
	~Cat()
	{
		cout<<"猫析构了"<<endl;
	}
	
	//自定义一个拷贝构造函数--》目的为了看清楚拷贝构造函数是否调用了
	Cat(const Cat &other)
	{
		cout<<"other的地址: "<<&other<<endl;
		cout<<"自定义的拷贝构造函数调用了"<<endl;
	}
	
	void show()
	{
		cout<<"年龄: "<<age<<" 体重: "<<weight<<endl;
	}

private:
	int age;
	float weight;
};

//定义一个函数,把猫的对象作为返回值
Cat fun(Cat c)  //Cat c=c1 
{	
	cout<<"彭老师定义的函数fun调用了"<<endl;
	return c;
}

int main()
{
	Cat c1;  //没有参数的猫构造了
	cout<<"main函数c1的地址: "<<&c1<<endl;
	//调用彭老师定义的函数fun()
	Cat ret=fun(c1);
}

 注意以下两种不会调用拷贝构造函数

Cat c1;
    Cat &c2=c1; //不会调用拷贝构造函数,c2是c1的引用(别名)
    Cat *c3=&c1; //不会调用拷贝构造函数,c3是指针,不是新的对象

 深拷贝和浅拷贝

 浅拷贝:默认的拷贝构造函数实现的就是浅拷贝
                        如果你使用默认的拷贝构造函数,原来对象中有申请堆空间,新的对象会跟原来的对象共用同一块堆空间--》这种现象叫做浅拷贝
           深拷贝:程序员知道了浅拷贝的bug,自己动手写个拷贝构造函数,解决刚才浅拷贝的bug --》这种就叫做深拷贝

#include <iostream>
#include <cstring>
/*
	举例说明浅拷贝
*/
using namespace std;

class Cat
{
public:
	//构造函数
	Cat(int _age,const char *newname)
	{
		cout<<"带参数的猫构造了"<<endl;
		//初始化一下私有成员
		age=_age;
		//给私有私有成员分配一下堆空间
		name=new char[20];
		strcpy(name,newname);
	}
	
	//定义析构函数
	~Cat()
	{
		cout<<"猫析构了"<<endl;
	}
	
	void show()
	{
		cout<<"年龄: "<<age<<" 名字: "<<name<<endl;
	}
	
	//修改猫的年龄和名字
	void setAttr(int newage,const char *newname)
	{
		age=newage;
		strcpy(name,newname);
	}

private:
	int age;
	char *name;
};


int main()
{
	Cat c1(4,"旺财");  //构造函数
	Cat c2=c1;         //默认的拷贝构造函数
	Cat c3=c1;         //默认的拷贝构造函数
	/*
		默认的拷贝构造函数很"坑爹"
		Cat(Cat &other)
		{
			this->age=other.age;   // c2.age=c1.age;
			this->name=other.name; // c2.name=c1.name;
		}
	
	*/
	c1.show();
	c2.show();
	c3.show();
	//我想修改c1的年龄和名字
	c1.setAttr(5,"小花");
	//打印结果
	c1.show();
	c2.show();
	c3.show();
	
	
}

由图片可以得知浅拷贝的bug,我只想修改c1,结果c2和c3一起修改啦,这明显不是我们想要达到的目的。 

#include <iostream>
#include <cstring>
/*
	自己动手实现深拷贝
*/
using namespace std;

class Cat
{
public:
	//构造函数
	Cat(int _age,const char *newname)
	{
		cout<<"带参数的猫构造了"<<endl;
		//初始化一下私有成员
		age=_age;
		//给私有私有成员分配一下堆空间
		name=new char[20];
		strcpy(name,newname);
	}
	
	//定义析构函数
	~Cat()
	{
		//释放堆空间
		delete []name;
		cout<<"猫析构了"<<endl;
	}
	
	//自定义拷贝构造函数-->实现深拷贝
	Cat(Cat &other)
	{
		this->age=other.age;
		//单独给当前对象的name指针分配堆空间
		this->name=new char[20];
		strcpy(this->name,other.name);
	}
	
	void show()
	{
		cout<<"年龄: "<<age<<" 名字: "<<name<<endl;
	}
	
	//修改猫的年龄和名字
	void setAttr(int newage,const char *newname)
	{
		age=newage;
		strcpy(name,newname);
	}

private:
	int age;
	char *name;
};


int main()
{
	Cat c1(4,"旺财");  //构造函数
	Cat c2=c1;         //自定义的拷贝构造函数
	Cat c3=c1;         //自定义的拷贝构造函数
	
	c1.show();
	c2.show();
	c3.show();
	//我想修改c1的年龄和名字
	c1.setAttr(5,"小花");
	//打印结果
	c1.show();
	c2.show();
	c3.show();
	
	
}

由图片得知,自己动手实现拷贝构造函数即可解决bug。

补充一个小知识点

重新认识cout和cin
========================================
   1.cout --》ostream类的对象
      cin   --》istream类的对象
          class  ostream  //输出流类
          class  istream   //输入流类
     namespace std
    {
             ostream  cout;
             istream   cin;
    } 

 类的继承与派生

1.概念和作用
          生活中继承:生活中  王思聪继承王健林的万达集团
                             儿子可以使用父亲提供的资源
          C++中继承:让子类可以使用父类提供的资源(成员函数),提高代码的复用性
                              C++的继承用来描述is-a这种关系
                                   猫是动物   Cat is an animal
                                   
          子类(派生类):A继承了B   A就是B的子类
          父类(基类):  A继承了B   B就是A的父类
          继承和派生:  A继承了B     B派生出A

   2.语法规则
                class 子类的名字:public  父类的名字        //公有继承
                class 子类的名字:private 父类的名字        //私有继承
                class 子类的名字:protected 父类的名字    //保护继承
                {
                        子类的代码

               };

   3.子类继承了父类,究竟可以使用父类的哪些成员
                                                             公有继承
                                       子类的内部                          子类的外部
      父类的公有成员             ok                                        ok
      父类的私有成员             no                                        no
      父类的保护成员             ok                                        no

                                                             私有继承
                                       子类的内部                          子类的外部
      父类的公有成员             ok                                        no
      父类的私有成员             no                                        no
      父类的保护成员             ok                                        no        

                                                             保护继承
                                       子类的内部                          子类的外部
      父类的公有成员             ok                                        no
      父类的私有成员             no                                        no
      父类的保护成员             ok                                        no         

   4.子类的大小(为什么子类的大小要包含父类的大小)
              子类的大小=父类所有成员变量的大小+子类自己成员变量的大小    也要满足字节对齐 

  5.继承以后构造函数和析构函数的调用顺序(不难,很重要)
         (1)构造函数有关知识点
                 知识点一: 构造函数的调用顺序: 先构造父类(默认是调用父类的无参构造函数),再构造子类
                                   如果程序员没有写无参构造函数(系统默认的无参构造函数也没有生成),会报错
                 底层原理:C++的编译器在创建子类对象的时候,会在子类对象的地址空间中先构造一个父类的副本,然后再去构造子类对象(整个子类对象中包含了两部分--》一部分是父类的副本,一部分是子类自己)
                 为什么要在子类对象的地址空间中弄个父类的副本呢??: 继承以后子类对象可以调用父类的成员方法(只是表面现象,本质上是通过子类对象中隐藏的那个父类副本去帮助你调用)

                知识点二:程序员指定使用父类的某个版本的构造函数
                                    子类构造函数():父类某个的构版本造(实参)
                                     {

                                    }

        (2)析构函数有关知识点
               知识点一: 析构函数的调用顺序: 先析构子类,再析构父类

   6.子类和父类出现同名方法
              子类对象.函数名();                   //默认调用子类自己的同名方法 
              子类对象.父类名字::函数名();   //指定调用父类的同名方法
                    Cat c1;
        c1.eat();
                    c1.Animal::eat();

 补充知识点

   1.class和struct区别
          区别一:class定义类,struct定义结构体
          区别二:class定义类,没有使用权限修饰,默认都是私有的
                       struct定义类,没有使用权限修饰,默认都是公有的

  2.对象数组
            struct student   array[10];   //结构体数组
            int  array[10];                     //整型数组
            Cat array[10];                    //对象数组,可以存放10个Cat的对象

#include <iostream>

using namespace std;

/*
	感受继承的好处
*/

class Animal
{
public:
	void eat()
	{
		cout<<"动物吃"<<endl;
	}
	int age;
private:
	void sleep()
	{
		cout<<"动物睡觉"<<endl;
	}
	float weight;
protected:
	void run()
	{
		cout<<"动物跑"<<endl;
	}
	int color;
};
//猫继承了动物
class Cat:public Animal
{
public:
	void test()
	{
		//子类的内部可以直接使用父类的所有公有成员
		//eat();
		//age=6;
		
		//子类的内部不能使用父类的所有私有成员
		//sleep();
		//weight=10.5;
		
		//子类的内部可以使用父类的所有保护成员
		run();
		color=0xffffff;
	}
};

int main()
{
	Cat c1;
	c1.test();
	//子类的外部可以直接使用父类的所有公有成员
	//c1.eat();
	//c1.age=6;
	
	//子类的外部不能使用父类的所有私有成员
	//c1.sleep();
	//c1.weight=10.5;
	
	//子类的外部不能使用父类的所有保护成员
	//c1.run();
	//c1.color=0xffffff;
}

 多重继承

 1.单继承和多重继承的区别
                单继承:子类只有一个父类
                多重继承:
                                         情况一:一个子类同时继承了多个父类(有多个父类)
                                                                  骡子:  马和驴
                                                                  圆桌:  圆形和桌子
                                         情况二:A继承了B,B继承C,C还可以继续继承............
                                                                 A --》波斯猫
                                                                 B --》猫
                                                                 C --》动物
                                                      直接父类:猫是波斯猫的直接父类
                                                      间接父类:动物是波斯猫的间接父类
                                                      特殊情况:环状继承
                                                                         A  动物
                                                              猫科 B    C  哺乳
                                                                         D 豹子
   2.语法规则
                class 子类名字:public 父类1,public 父类2
               {
 
               };
               子类大小:所有父类大小之和+子类自身大小   满足字节对齐             
               构造析构调用顺序:先构造父类(多个父类按照继承的先后顺序,从左到右构造),在构造子类
                                             先析构子类,在析构父类(多个父类按照继承的先后顺序,从右到左析构)
               指定调用父类的构造函数:
                                             用参数列表去指定
               子类出现跟父类同名的方法:

   3.虚继承解决多重继承遇到的bug(重点)


               多重继承遇到bug(环状继承):  二义性和Animal被构建多次,浪费存储空间
               虚基类:A虚继承了B,B就是A的虚基类
               语法规则:class 子类:virtual public 父类
                                {

                                }

               总结:普通继承(没有使用virtual)和虚继承的区别
                                 第一:虚继承可以解决二义性和Animal被构建多次这两个问题,普通继承无法解决
                                 第二(核心关键):只要一个类虚继承了其他类,那么该类所有的对象中都会多出一个指针,这个指针专门用来指向虚基类表的首地址
               虚基类表:C++中专门用来存放所有的虚基类入口地址的一种数据结构
 

#include <iostream>

using namespace std;

/*
	程序员自己指定想要调用父类的哪个版本的构造函数
*/

class Animal
{
public:
	//父类有三个版本的构造函数
	Animal()
	{
		cout<<"父类Animal无参构造"<<endl;
	} 
	Animal(int n)
	{
		cout<<"父类Animal带一个参构造,参数是: "<<n<<endl;
	} 
	Animal(int n,float m)
	{
		cout<<"父类Animal带两个参构造,参数是: "<<n<<"   "<<m<<endl;
	} 
};
//猫继承了动物
class Cat:public Animal
{
public:
	//写法一:
	Cat(int _n):Animal(_n)
	{
		cout<<"写法一子类的构造函数利用参数列表,指定调用父类的Animal(int n)"<<endl;
	}
	
	//写法二:
	Cat(int _n,float _m):Animal(_n,_m)
	{
		cout<<"写法二子类的构造函数利用参数列表,指定调用父类的Animal(int n,float m)"<<endl;
	}
	
	//写法三:
	Cat():Animal(6,7.8)
    {
		cout<<"写法三子类的构造函数利用参数列表,指定调用父类的Animal(int n,float m)"<<endl;
	}
	
	//写法四:
	Cat(int _color,int _n,float _m):Animal(_n,_m)
	{
		color=_color;
		cout<<"写法四子类的构造函数利用参数列表,指定调用父类的Animal(int n,float m)"<<endl;
	}
private:
	int color;

};

int main()
{
	//Cat c1(4);              //跟写法一对应
	//Cat c2(5,7.5);         //跟写法二对应
	//Cat c3;               //跟写法三对应
	Cat c4(0xffffff,3,4.5);//跟写法四对应
}

 子类的构造函数利用参数列表的形式,指定调用父类的构造函数。

#include <iostream>

using namespace std;

class Animal
{
public:
	Animal()
	{
		cout<<"Animal构造了"<<endl;
	}
};

//猫科动物
class Catamount:public Animal
{
public:
	Catamount()
	{
		cout<<"Catamount构造了"<<endl;
	}
};

//哺乳动物
class Mammal:public Animal
{
public:
	Mammal()
	{
		cout<<"Mammal构造了"<<endl;
	}
};

//豹子
class Leopard:public Catamount,public Mammal
{
public:
	Leopard()
	{
		cout<<"Leopard构造了"<<endl;
	}
};

int main()
{
	Leopard l1;
}

由图片可知animal构造啦两次很浪费存储空间。

#include <iostream>

using namespace std;

class Animal
{
public:
	void eat()
	{
		cout<<"动物吃"<<endl;
	}
};

//猫科动物
class Catamount:public Animal
{
	
};

//哺乳动物
class Mammal:public Animal
{
	
};

//豹子
class Leopard:public Catamount,public Mammal
{
	
};

int main()
{
	Leopard l1;
	//编译器搞不清楚你究竟是想用猫科动物构建出来的Animal副本去调用eat
	//还是说用哺乳动物构建出来的Animal副本去调用eat
	//l1.eat();  //有歧义(二义性)
	
	//解决方法一:
	l1.Catamount::eat();
	l1.Mammal::eat();
	
	//解决方法二:虚继承来解决
}
#include <iostream>

using namespace std;

class Animal
{
public:
	void eat()
	{
		cout<<"动物吃"<<endl;
	}
	Animal()
	{
		cout<<"Animal构造了"<<endl;
	}
};

//猫科动物
class Catamount:virtual public Animal
{
public:
	Catamount()
	{
		cout<<"Catamount构造了"<<endl;
	}
};

//哺乳动物
class Mammal:virtual public Animal
{
public:
	Mammal()
	{
		cout<<"Mammal构造了"<<endl;
	}
};

//豹子
class Leopard:public Catamount,public Mammal
{
public:
	Leopard()
	{
		cout<<"Leopard构造了"<<endl;
	}
};

int main()
{
	Leopard l1;
	l1.eat();
}

由图片得知,使用虚继承完美解决animal构造多次以及二义性的问题。

虚继承后,类的大小也发生啦改变

#include <iostream>

using namespace std;

class Animal
{
public:
	int age;
};

//猫科动物
class Catamount:virtual public Animal
{

	//隐藏一个指针--》8字节
};

//哺乳动物
class Mammal:virtual public Animal
{

	//隐藏一个指针--》8字节
};

//豹子
class Leopard:public Catamount,public Mammal
{

	
};

int main()
{
	cout<<"Animal大小: "<<sizeof(Animal)<<endl;   //4
	cout<<"Catamount大小: "<<sizeof(Catamount)<<endl;   //16
	cout<<"Mammal大小: "<<sizeof(Mammal)<<endl;   //16
	cout<<"Leopard大小: "<<sizeof(Leopard)<<endl; //24
	
}

补充知识点

 1.某些人很喜欢在类的定义中直接给成员变量赋值(以前老的C++语法标准中是禁止这么做的)
     现在C++11新标准允许你这么做
          struct student
          { 
                  char name[10];
                  int age;
          }
  
   2.借书还书的练习题
          书中的成员变量私自改成了公有的(原因是我想在类的外部直接使用这些成员)
          后面学习C++中的友元--》可以更好的解决这个问题
          书籍对象不能直接用==比较是否相同--》后面学习C++中的运算符重载也能解决这个问题

   3.经典错误认识
          强制类型转换是全世界无敌的---》🙂🙂🙂o(╥﹏╥)o
          任何类都是可以继承的---》🙂🙂🙂o(╥﹏╥)o

 类的组合(类的包含)

 1.概念定义
               类的组合: 一个类的对象作为另外一个类的成员变量
               作用:has-a 关系 (拥有关系)
     举例理解概念
               例子一: 读者借了5本书    
               例子二: xxx拥有三套房子    

#include <iostream>

using namespace std;

class Book
{
public:
	Book()
	{
		
	}
	Book(string _name,string _booknum,string _author)
	{
		bookname=_name;
		booknum=_booknum;
		bookauthor=_author;
	}
	string bookname;
	string booknum;
	string bookauthor;
};

class Reader
{
public:
	Reader(string _name,string _num)
	{
		n=0; 
		readername=_name;
		readernum=_num;
	}
	//借书
	void getBook(Book &b)
	{
		if(n<=4)
		{
			bookarray[n++]=b;
		}
		else
			cout<<"对不起,每个人最多只能借阅5本书!\n";
		
	}
	//还书
	void putBook(Book &b)
	{
		int i,j;
		//找到要还的书
		for(i=0; i<n; i++)
		{
			if(bookarray[i].bookname==b.bookname)  //找到要还的书
			{
				for(j=i; j<n-1; j++)
					bookarray[j]=bookarray[j+1];
				n--;
			}
		}
	}
	//查询打印读者的借书信息
	void show()
	{
		int i;
		cout<<"读者: "<<readername<<"借阅的书籍如下: "<<endl;
		for(i=0; i<n; i++)
			cout<<"书名: "<<bookarray[i].bookname<<"  作者: "<<bookarray[i].bookauthor<<endl;
	}
private:
	string readername;  //读者姓名
	string readernum;   //读者编号
	Book bookarray[5];  //存放我借到的书籍
	int n;  //保存下标
};

int main()
{
	//准备几本书
	Book b1("红楼梦","001","曹雪芹");
	Book b2("西游记","002","吴承恩");
	Book b3("水浒传","003","施耐庵");
	Book b4("三国演义","004","罗贯中");
	Book b5("小时代","005","郭敬明");
	
	//定义读者
	Reader r1("马云","1");
	Reader r2("马化腾","2");
	
	//马云借书
	r1.getBook(b1);
	r1.getBook(b2);
	r1.getBook(b3);
	r1.getBook(b4);
	r1.getBook(b5);
	//r1.getBook(b5);
	
	//打印马云的借阅信息
	r1.show();
	
	//马云还书
	r1.putBook(b1);
	
	cout<<"============================"<<endl;
	//打印马云的借阅信息
	r1.show();
	
	
}

 const和static的用途(技术面试常问问题)

  1.const的用法
            用法一:const修饰成员变量(常量)
                         初始化的写法只有两种:
                                   写法一:在类的定义中直接给const修饰的常量赋值(C++11标准)
                                   写法二:在构造函数的参数列表中给const修饰的常量赋值
                                                Circle(double _r):pi(3.14)
                                   {
                             r=_r;
                                   }
            用法二:const修饰成员函数
                         表示该函数的代码中不可以修改任何成员变量的值
                         语法规则:
                                    void show() const
                                  {

                                   }
                         区分const放在函数的前面和放在函数的后面有啥区别: 
                                   const int show()   //const放在函数的前面,用来修饰函数的返回值,表示该函数的返回值是个int类型的常量
                                   {

                                   }
                                    int show() const  //const放在函数的后面,用来修饰函数,表示该函数不可以修改任何成员变量的值
                                    {

                                    }
                                    const int show() const  //前面两种情况的综合
                                    {

                                    }
                          实际开发中的用途:防止成员函数任意修改成员变量的值
            用法三:const修饰引用--》常引用

   2.static的用法(经典常问知识点)
            类中的静态成员:只要类中的成员使用了static修饰,这个成员就是静态成员
            静态成员优先于类的对象而存在,你还可以创建对象,静态成员已经存在了
            用法一:修饰成员变量("类中的全局变量")
                         特点一:
                                 静态成员变量可以通过类名直接调用(当然也可以使用对象去调用)
                                 非静态成员变量只能用对象去调用,不能用类名直接调用
                        特点二:
                                 只能在类的外部初始化(初始化的时候不能加上static),其他方法都不行
                        特点三:
                                 static修饰的成员变量是这个类所有的对象共享的(该类所有的成员变量用的是同一个静态成员变量)
                                 实际开发中的用途:用于统计创建的对象数量
                                                               用于对象之间传递参数
                        静态成员变量的出现,影响了类的大小
                                类的大小:只跟类中所有非静态成员变量的大小有关,跟静态成员变量无关
            用法二:修饰成员函数
                       特点一:
                                静态成员函数可以直接通过类名来调用(不创建对象也行)
                       特点二:
                                静态成员函数代码中只可以使用静态变量(非静态成员变量不能使用)

#include <iostream>

using namespace std;

class Circle
{
public:
	
	/* Circle(double _r)
	{
		//pi=3.14;  //无法初始化,语法错误
		r=_r;
	} */
	
	/* void setAttr(const double _pi)
	{
		pi=_pi;   //无法初始化,语法错误
	} */
	
	Circle(double _r):pi(3.14)
	{
		r=_r;
	}
	
private:
	const double pi;  //π
	double r;  //半径
};

int main()
{
	Circle c1(1.2);
	//c1.setAttr(3.14);
}
#include <iostream>

using namespace std;

class Circle
{
public:
	
	Circle(double _r)
	{
		r=_r;
	} 
	
	//打印半径的值
	void show() const
	{
		//偷偷摸摸干了坏事,把半径修改了
		r=3.2;  //语法错误,const修饰成员函数,该函数内部不可以修改任何成员变量的值
		cout<<"这个圆形的半径是: "<<r<<endl;
	}
	
	
private:
	double r;  //半径
};

int main()
{
	Circle c1(1.2);
	c1.show();
	
}
#include <iostream>

using namespace std;

class Cat
{
public:
	Cat(int _age)
	{
		age=_age;
		n++;
	}
	
	//证明:静态成员变量n是所有对象共有的,非静态成员变量每个对象都有各自独立的
	void show()
	{
		cout<<"age的地址是: "<<&age<<"  n的地址是: "<<&n<<" n的值是: "<<n<<endl;
	}
	
	//封装一个函数用于对象之间传递参数
	void putarg(int _n)
	{
		n=_n;
	}
	static int n; //静态成员变量
	int age;
};
//静态成员变量必须在类的外部初始化
int Cat::n=0;

int main()
{
	Cat c1(5);
	Cat c2(6);
	Cat c3(7);
	Cat c4(8);
	
	//正常情况下:打印静态成员变量的值,可以直接通过类名调用,不需要用对象调用
	//cout<<Cat::n<<endl;  //正确的
	
	//小明说:我想用对象去调用
	cout<<c1.n<<endl;      //正确的
	
	//非静态成员只能通过对象去调用
	//cout<<Cat::age<<endl;  //错误的
	
	//调用show方法  证明:静态成员变量n是所有对象共有的,非静态成员变量每个对象都有各自独立的
	c1.show();
	c2.show();
	c3.show();
	c4.show();
	
	//c1想传递一个整数给c2
	c1.putarg(666);
	c2.show();
	
}

 

#include <iostream>

using namespace std;

class Cat
{
public:
	Cat(int _age)
	{
		age=_age;
		n++;
	}
	
	
	static void show()
	{
		cout<<"n is: "<<n<<endl;  //正确的,静态成员函数中只能使用静态成员变量
		//cout<<"age is: "<<age<<endl;  //错误的,静态成员函数中只能使用静态成员变量
	}
	
private:
	static int n; //静态成员变量
	int age;
};
//静态成员变量必须在类的外部初始化
int Cat::n=0;

int main()
{
	//静态成员函数通过类名直接调用(常用的写法)
	Cat::show();
	
	//静态成员函数也可以用对象去调用
	Cat c1(5);
	c1.show();
	
}

 静态成员变量的出现影响啦类的大小

#include <iostream>

using namespace std;

class Cat
{
public:
	Cat(int _age)
	{
		age=_age;
		n++;
	}
	static int n; //静态成员变量
	int age;
};
//静态成员变量必须在类的外部初始化
int Cat::n=0;

int main()
{
	cout<<"Cat类大小是: "<<sizeof(Cat)<<endl;  //4
}

多态和虚函数(重点) 

1.引入多态
            字面上理解多态:多种表现形式,比如:例子中,父类Animal的eat()方法在不同的子类中具备不同的表现形式(同一个函数名,父类和子类代码不同),现在希望传递不同的子类对象可以调用不同子类的同名方法
            父类和子类具有同名方法,传递不同的子类对象可以调用不同子类中的同名方法--》多态
            多态要解决的两个问题:
                   问题一:形参具有通用性
                   问题二:传递不同的子类对象作为实参,要求能够调用对应子类的同名方法
     为了实现多态,C++语言的设计者做好了如下几点准备:
            第一点:C++允许父类的指针或者父类的引用指向不同的子类对象(不需要做任何转换,直接就能使用)

   2.实现多态--》虚函数实现多态(虚函数为了多态而生的)
            语法规则:    virtual  函数返回值  函数名字(形参)
                                {
                                       函数的代码
                               }

   3.多态的特点和要求
                  第一:必须要有继承,没有继承就没有多态
                  第二:子类必须重写(复写,覆盖)父类的同名方法
                  第三:父类的同名方法必须定义成虚函数
                  第四:父类的同名方法定义成虚函数,子类的同名方法默认也是虚函数(子类同名方法的前面不需要写virtual,写上也可以,写不写无所谓)

   4.虚函数(多态)的底层原理(跟虚继承的底层原理类似,但是属于两个不同的知识点)
                 底层原理:只要一个类中定义了虚函数,该类的所有对象中都会多出一个指针,该指针用于指向虚函数表的首地址
                 虚函数表:C++中用来存放所有虚函数地址的一种数据结构

   5.小结
                C++中的四大"虚"
                第一:虚继承和虚基类
                第二:虚函数和多态
                第三:纯虚函数和抽象类
                第四:虚析构

   6.多态的分类
                分成两种:运行时多态,编译时多态
                编译时多态:指的就是前面我们学习过函数重载
                                            add() --》名字相同,add具备不同的表现形式
                运行时多态:我们今天学习的这种,用虚函数来实现的多态

#include <iostream>

using namespace std;

class Animal   
{
public:
	virtual void eat()  
	{
		cout<<"动物吃"<<endl;
	}
};

class Dog:public Animal
{
public:
	void eat()
	{
		cout<<"狗啃骨头"<<endl;
	}
};

class Cat:public Animal
{
public:
	void eat()
	{
		cout<<"猫吃鱼"<<endl;
	} 
};

class Bscat:public Cat
{
public:
	void eat()
	{
		cout<<"波斯猫吃猫粮"<<endl;
	}
};

class Sheep:public Animal
{
public:
	void eat()
	{
		cout<<"羊吃草"<<endl;
	}
};

//封装一个函数--》该函数可以展示各种动物吃什么东西
//void show_animaleat(形参具有通用性)
//void show_animaleat(Dog &d)  //具有局限性--》只能传递狗的引用
//void show_animaleat(Cat &d)  //具有局限性--》只能传递猫的引用
//void show_animaleat(Sheep &d)  //具有局限性--》只能传递羊的引用
//void show_animaleat(Animal &d)  //参数具有通用性--》只要属于Animal的子类,都可以当成实参传递
//void show_animaleat(Animal *d)   //参数具有通用性--》只要属于Animal的子类,都可以当成实参传递 
void show_animaleat(Animal d)    //参数具有通用性--》只要属于Animal的子类,都可以当成实参传递 
{
	d.eat();
	//d->eat();
}

int main()
{
	//四个不同的子类对象
	Dog d1;
	Cat c1;
	Sheep s1;
	Bscat b1;
	
	//对应传递引用的版本--》实现了多态
	// show_animaleat(d1);  //Animal &d=d1;
	// show_animaleat(c1);  //Animal &d=c1; 
	// show_animaleat(s1);  //Animal &d=s1; 
	// show_animaleat(b1);  //Animal &d=b1;
	
	//对应传递指针的版本--》实现了多态
	// show_animaleat(&d1);  //Animal *d=&d1;
	// show_animaleat(&c1);  //Animal *d=&c1; 
	// show_animaleat(&s1);  //Animal *d=&s1; 
	// show_animaleat(&b1);  //Animal *d=&b1;
	
	//对应传递对象的版本--》无法实现多态
	show_animaleat(d1);  //Animal d=d1;
	show_animaleat(c1);  //Animal d=c1; 
	show_animaleat(s1);  //Animal d=s1; 
	show_animaleat(b1);  //Animal d=b1;
}

 补充知识点

 1.父类和子类有同名函数(父类的同名函数不是虚函数)
      看赋值运算左边是什么类型的指针,就调用谁的同名函数
            父类指针  = 子类的地址;
            父类指针->同名函数;    //调用的是父类自己的同名函数

            子类指针  = (强转)父类的地址;
            子类指针->同名函数;    //调用的是子类自己的同名函数 

    2.父类和子类有同名函数(父类的同名函数是虚函数)
       看赋值运算右边是什么类型的指针,就调用谁的同名函数
            父类指针  = 子类的地址;
            父类指针->同名函数;    //调用的是子类自己的同名函数

            子类指针  = (强转)父类的地址;
            子类指针->同名函数;    //调用的是父类自己的同名函数 

#include <iostream>

using namespace std;

class B
{
public:
	void fun()
	{
		cout<<"B中的fun"<<endl;
	}
};

class D:public B
{
public:
	void fun()
	{
		cout<<"D中的fun"<<endl;
	}	
};

int main()
{
	B b,*pb;
	D d,*pd;
	
	//父类的指针指向子类对象的地址(不需要做任何类型转换)
	pb=&d;
	pb->fun(); //父类的fun
	
	
	//子类的指针指向父类对象的地址(一定要做类型转换)
	pd=(D *)(&b);
	pd->fun();  //子类的fun
}

 虚析构函数(把析构函数定义成虚函数)

1.作用
               父类的指针指向不同的子类对象,某些程序员天真地认为delete父类的指针,就等价于把子类对象释放了(这是错误的,真实的情况是C++只会调用父类的析构函数,不会调用子类的析构函数,从而导致子类析构的不彻底)
               解决方法: 把父类的析构函数定义成虚析构就可以了

#include <iostream>

using namespace std;

class Animal
{
public:
	Animal()
	{
		cout<<"Animal无参构造"<<endl;
	}
	virtual ~Animal()
	{
		cout<<"Animal析构"<<endl;
	}
};

class Cat:public Animal
{
public:
	Cat()
	{
		//给私有成员分配堆空间
		name=new char[20];
		cout<<"猫的构造函数中申请了堆空间"<<endl;
	}
	~Cat()
	{
		//释放堆空间
		delete []name;
		cout<<"猫的析构函数中释放了堆空间"<<endl;
	}
private:
	char *name;
};

class BSCat:public Cat
{
public:
	BSCat()
	{
		//给私有成员分配堆空间
		str=new char[20];
		cout<<"波斯猫的构造函数中申请了堆空间"<<endl;
	}
	~BSCat()
	{
		//释放堆空间
		delete []str;
		cout<<"波斯猫的析构函数中释放了堆空间"<<endl;
	}
private:
	char *str;	
};



int main()
{
	//cout<<"Animal大小是: "<<sizeof(Animal)<<endl;
	//创建一个子类对象--》正常写法,完全没有bug的写法,自己申请,自己释放
	//Cat *c1=new Cat();
	//delete c1;
	
	//有问题的写法(没有加virtual)
	//Cat *c1=new Cat();
	//定义一个父类的指针,指向你刚才申请的子类的堆空间
	//Animal *p=c1;
	//cout<<"p指向的地址: "<<p<<endl;
	//cout<<"c1指向的地址: "<<c1<<endl;
	//delete p;  //此时只会调用父类的析构,不会调用子类的析构
	
	//加了virtual就没有任何问题
	BSCat *b=new BSCat();
	Animal *p=b;
	delete p;
}

 纯虚函数和抽象类

 1.纯虚函数
               纯虚函数是虚函数的一种特例
               特点:纯虚函数没有代码,只要一个声明
                         纯虚函数=0
               语法规则:
                          virtual  函数的返回值   函数名字(形参)=0;
               作用:在实际开发中用来提供统一的接口,让项目组的所有码农依照统一接口去实现具体的代码
               用法:把纯虚函数全部都放在一个类的定义中,然后子类去继承这个类,把所有的纯虚函数的代码实现出来

    2.抽象类
              定义:只要一个类中声明了纯虚函数,这个类就是抽象类
              特点:
                        第一个:抽象类不能创建任何对象,抽象类只能被继承
                        第二个:子类继承了抽象类,必须把所有的纯虚函数都实现出来,如果漏掉任何一个纯虚函数没有实现,子类依然是个抽象类
 

#include <iostream>

using namespace std;

//项目组的组长--》定义了一个类,这个类把整个项目需要统一的接口函数全部都声明好
//定义了抽象类Funinterface
class Funinterface  //只是对外提供统一的接口,如何实现代码,交给程序员自己去完成
{
public:
	virtual int compare()=0;
	virtual int show()=0;
};

class Cat:public Funinterface
{
public:
	//实现纯虚函数的具体代码
	int compare()
	{
		cout<<"猫比较年龄大小"<<endl;
	}
	int show()
	{
		cout<<"show猫"<<endl;
	}
};

class Dog:public Funinterface
{
public:
	//实现纯虚函数的具体代码
	int compare()
	{
		cout<<"狗比较体重大小"<<endl;
	}
	int show()
	{
		cout<<"show狗"<<endl;
	}
};
int main()
{
	//错误:抽象类不能创建任何对象
	//Funinterface inter;
	
	Cat c1;
	c1.compare();
	//c1.show();
	Dog d1;
	d1.compare();
	d1.show();
}
#include <iostream>

using namespace std;

class Base
{
public:
	void virtual fun()
	{
		cout<<"我是一个虚函数"<<endl;
	}
	void virtual otherfun()=0;
};

class Son:public Base
{
public:
	void fun()
	{
		cout<<"子类中的fun,我是一个虚函数"<<endl;
	}
	void otherfun()
	{
		cout<<"子类中的otherfun"<<endl;
	}
};

int main()
{
	Son s;
}

注意,虚函数、纯虚函数virtual可以写在返回值的后面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hqb_newfarmer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值