指针
1函数声明与定义:
函数声明可以很多次,但是定义只能一次。
函数的声明
int max(int a,int b)
函数的定义
int max(int a,int b)
{
return a>b?a:b;
}
指针就是一个地址
定义指针
int *p; //p为指针
p=&a; //与a建立关系
*p; //解引用
指针在32位(x86)下占4个字节空间 64位下占8个字节
2空指针和野指针:
空指针:指针变量指向内存中编号为0的空间
用途:初始化指针变量
**注意:**空指针指向的内存是不可以访问的
int * p=NULL; // 定义空指针
野指针:指针指向非法内存空间
3const修饰指针:
const修饰指针又三种情况:
1.const修饰指针 —常量指针
2.const修饰常量 ----指针常量
3.const即修饰指针,又修饰常量
//特点: 指针的指向可以修改,但是指针指向的值不可以改
const int * p=&a; 常量指针
//特点:指针的指向不可以改,指针指向的值可以改
int *const p=&a; //指针常量
//特点:指针的指向不可以改,指针指向的值不可以改
const int *const p3=&a; //修饰指针也修饰常量
4指针与数组:
利用指针访问数组:
int arr[10]={1,2,3,4,5,6,7,8,9,10};
cout<<"第一个元素为:"<<arr[0]<<endl;
int *p=arr; //arr就是数组首地址
cout<<"利用指针访问第一个元素:"<<*p<<endl; //我在自己电脑上试,p++也是4个字节
p++;//指针向后偏移4个字节
cout<<"访问第二个字节"<<*p<<endl;
for(inti=0;i<10;i++)
{
//利用指针遍历数组
cout<<*p<<endl;
p++;
}
5指针和函数:
void swap(int *p1,int *p2)
{
int temp=*p1;
*p1=*p2;
*p2=temp;
}
int a=10;
int b=20;
swap(&a,&b)
指针,数组,函数
**案例描述:**封装一个函数,利用冒泡排序,实现对整形数组的升序排序
//冒泡排序函数
void bubblesort(int *arr,int len)//int *arr也可以写成int arr[],就是数组的首地址
{
for(int i=0;i<len-1;i++)
{
for(int j=0;j<len-i-1;j++)//有问题
{
for(arr[j]>arr[j+1])
{
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
结构体
**语法·:**struct 结构体名{结构体成员列表};
通过结构体创建变量的方式有三种:
#include"string"
struct Student{
//成员列表
string name;
int age;
int score;
};
struct Student s1; //struct 可以省去
//给s1属性赋值,通过访问结构体变量中的属性
//创建结构体变量时,struct 关键字可以省去
s1.name="张三";
s1.age=5;
s1.score=100;
//第二种定义方式
struct Student s2={"张三",5,100};
//第三种定义方式不常用,就不写了
1.结构体数组
作用:将自定义结构体放入到数组中方便维护
**语法:**struct 结构体名 数组名[元素个数]={{},{},…{}}
//结构体定义
struct student
{
//成员列表
string name;
int age;
int score;
};
int main()
{
//创建结构体数组
struct student arr[3]=
{
{"张三",18,80},
{"李四",19,88},
{"王五",17,100}
}
//修改张三的分数
arr[0].score=78;
//遍历结构体数组
for(int i=0;i<3;i++)
{
cout<<arr[i].name<<arr[i].age<<arr[i].score<<endl;
}
}
2结构体指针
作用:通过指针访问结构体中的成员
-
利用操作符->可以通过结构体指针访问结构体属性
//结构体定义 struct student { //成员列表 string name; int age; int score; }; int main() { //创建学生结构体变量 student s={"张三",18.100}; //通过指针指向结构体变量 student *p=&s; //通过指针访问结构体变量中的数据 //通过结构体指针访问结构体中的属性,需要用到'->' cout<<"姓名:"<<p->name<<p->age<<endl; }
3结构体嵌套结构体:
struct teacher
{
int id;
string name;
int age;
struct student stu;
};
int main()
{
teacher t;
t.id=1000;
t.name="老王";
t.age=50;
t.stu.age=20;
}
4结构体做函数参数
**作用:**将结构体作为参数向函数中传递
传递方式有两种:
-
值传递
-
地址传递
//地址传递 void printStudent1(struct student s) { }
引用
**作用:**给变量起别名
语法:数据类型 &别名=原名
int a=10;
int &b=a; //引用
int &b;//错误
-
引用必须初始化
-
引用一旦初始化,就不可以更改了
1引用做函数参数
**作用:**函数传参时,可以利用引用的技术让形参修饰实参
**优点:**可以简化指针修改实参
用地址传递
//形参修饰实参 用地址传递
void mySwap02(int *a, int *b )
{
int temp =*a;
*a = *b;
*b =temp;
}
传引用
//传引用 也是形参修饰实参
int mySwap03(int &a,int &b)
{
int temp =a;
a=b;
b=temp;
}
2用做函数的返回值
- 不要返回局部变量的引用
- 函数的调用可以作为左值
int& test02()
{
static int a=10; //静态变量,存放在全局区,全局区上的数据在程序结束后系统释放
return a;
}
int main()
{
int &ref2=test02();
cout<<"ref2="<<ref2<<endl; //ref2=10
test02()=1000;
cout<<"ref2="<<ref2<<endl; //ref2=1000
}
3引用的本质
**本质:**指针常量 所以一旦初始化就不能更改
//发现是引用,转化为int* const ref =&a;
void func(int& 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;
}
4常量引用
**作用:**常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
//引用使用的场景,通常用来修饰形参
void showValue(const int& val)
{
// 不能修改数据 val=1000 值被修改会报错
cout<<"val:"<<val<<endl;
}
int main()
{
int a=100;
showValue(a);
}
函数提高
1函数默认参数
//函数默认参数
//如果我们传入了数据,就用自己的数据,如果没有,那么就用默认值
//如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有
int fubc(int a,in b=2,int c=7);
//如果函数的声明有默认参数,函数实现就不能有默认参数,函数声明和实现中只能有一个默认值,
int func2(int a=10,int b=10);//函数声明
int func2(int a,int b) //函数实现
{
return a+b;
}
2函数占位参数
c++中函数的形参列表里可以有占位参数,用来占位,调用函数时必须填补该位置
语法:返回值类型 函数名(数据类型){}
//函数占位参数,占位参数也可以有默认参数
void func(int a,int){ //第二个int就是占位
cout<<"this is func"<<endl;
}
int main()
{
func(10,10);//占位参数必须填补
return 0;
}
函数重载
1函数重载概述
**作用:**函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同,或者个数不同,或者顺序
void func()
{
cout<<"函数调用"<<endl;
}
void func(int a)
{
cout<<"函数调用"<<endl;
}
int main()
{
func();
func(10);
}
注意: 函数的返回值不可以做函数重载的条件
2函数重载注意事项
- 引用作为重载的条件
- 函数重载碰到默认参数
//引用作为重载的条件
void fun(int &a)
{
cout<<"func(int &a)调用"<<endl;
}
void fun(const int &a) //
{
cout<<"func(const int &a)调用"<<endl;
}
int main()
{
func(10); //会调用const那个
//int a=10;
//func(a); 会调用第一个
}
类和对象
**三大特性:**封装,继承,多态
c++认为万事万物都皆为对象,对象上有其属性和行为
通过类创建一个具体的学生,叫做实例化;
1 封装
访问权限有三种:
- 公共权限 public 成员 类内可以访问 类外不可以访问
- 保护权限 protect 成员 类内可以访问 类外不可以访问 儿子也可以访问父亲中的保护内容
- 私有权限 private 成员 类内可以访问 类外不可以访问 儿子不可以访问父亲中的保护内容
2 struct 和class区别
- struct 默认权限是公共 public
- class 默认权限是私有 private
3 将成员属性设置为私有化
- 可以自己控制读写的权限
- 对于写可以检测数据的有效性
class Person
{
public:
void setName(string name )
{
m_Name=name;
return m_Name;
}
private:
string m_Name;
int m_Age;
string m_Lover;
};
int main()
{
person p;
p.setName("tyh");
cout<<"姓名为:"<< p.setName()<<endl;
}
4 构造函数的分类及调用
两种分类方式:
-
按参数分类:有参构造和无参构造
-
按类型分类:普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
// 拷贝构造函数 Person(const Person &p) { }
5 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生,如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符
构造函数调用规则:
-
如果用户定义有参构造函数,c++不再提供默认无参构造,但会提供默认拷贝构造
-
如果用户定义拷贝构造函数,c++不会提供其他构造函数
#include<iostream> using namespace std; class CExample { private: int a; public: //构造函数 CExample(int b) { a=b; printf("constructor is called\n"); } //拷贝构造函数 CExample(const CExample & c) { a=c.a; printf("copy constructor is called\n"); } CExample& operator =(const CExample &);//赋值运算符 //析构函数 ~CExample() { cout<<"destructor is called\n"; } void Show() { cout<<a<<endl; } }; int main() { CExample A(100); CExample B=A; B.Show(); return 0;
}
##### 6 拷贝构造函数
**拷贝构造函数**是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
* **拷贝构造函数**是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
* 复制对象把它作为参数传递给函数。
* 复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:
classname (const classname &obj) {
// 构造函数的主体
}
##### 7 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
##### 8 静态成员函数
静态成员函数就是在成员变量和成员函数中加上关键字static,称为静态成员
在类中,static 除了可以声明[静态成员变量](http://c.biancheng.net/view/2227.html),还可以声明静态成员函数。普通成员函数可以访问所有成员(包括成员变量和成员函数),静态成员函数只能访问静态成员。
静态成员分为:
* 静态成员变量
* 所有对象共享同一份数据
* 在编译阶段分配内存
* 类内声明,类外初始化
* 静态成员函数
* 所有对象共享同一个函数
* 静态成员只能访问静态成员变量,因为无法区分到底是哪个对象的
static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为声明的变量分配一份内存,所有对象使用的都是这份内存中的数据。
两种访问方式:1通过对象访问
2通过类名访问
static 成员变量既可以通过对象来访问,也可以通过类来访问。请看下面的例子:
//通过类类访问 static 成员变量
Student::m_total = 10;
//通过对象来访问 static 成员变量
Student stu(“小明”, 15, 92.5f);
stu.m_total = 20;
//通过对象指针来访问 static 成员变量
Student *pstu = new Student(“李华”, 16, 96);
pstu -> m_total = 20;
**注意:static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问。**
---------------------------------------------------------------------------------------------------------------------
##### 9 c++对象模型和this指针
* this指针指向被调用的成员函数所属的对象
* this指针不需要定义,直接使用即可
this指针是隐含每一个非静态成员函数内的一种指针。
this指针不需要定义,直接使用即可。
this指针的用途:
* 当形参和成员变量同名时,可用this指针来区分
* 在类的非静态成员函数中返回对象本身,可用return *this
---------------------------------------------------------------------------------------------------------------------
##### 10 友元函数
**友元的目的就是让一个函数或者类访问另一个类中私有成员**
关键字:friend
友元的三种实现
* 全局函数做友元
* 类做友元
* 成员函数做友元
---------------------------------------------------------------------------------------------------------------------
##### 11 重载函数
重载函数—加法:两对象相加
#include
using namespace std;
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 程序的主函数
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double volume = 0.0; // 把体积存储在该变量中
// Box1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的体积
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的体积
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把两个对象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的体积
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
---------------------------------------------------------------------------------------------------------------------
#### 继承
**语法:** class 子类 : 继承方式 父类
继承方式:
* 公共继承
* 保护继承
总结:
1 父类对象可以直接访问到子类中同名成员
2 子类对象加作用域可以访问父类同名成员
3 当子类与父类拥有同名的成员函数,子类会继承父类中同名成员函数,加作用域可以访问父类中同名函数
##### 2 继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致
* 访问子类同名成员函数 直接访问即可
* 访问父类成员函数 需要加作用域
##### 3 多继承语法
c++允许一个类继承多个类
语法:class子类:继承父类1,继承父类2......
多继承可能引发父类中同名成员出现,需要加作用域区分
**c++实际开发中不建议多继承**
class son:public base1,public base2
菱形继承问题:
class animal{};//动物类
class sheep:public animal{};//羊类
class tuo :public animal{};//驼类
class sheeptuo:public sheep ,public tuo{};
void test01()
{
sheeptuo st;
// 用作用域加以区分
st.sheep::age=18;
st.tuo::age=23;
//这份数据有两份,造成资源浪费,因此利用虚继承可以解决菱形继承的问题
}
虚继承:继承之前加上virtual 为虚继承
#### 多态
多态分为两类
* 静态多态:函数重载和运算符重载属于静态多态,复用函数名
* 动态多态:派生类和虚函数实现运行时多态(用的较多)
静态多态与动态多态区别:
* 静态多态地址的函数地址早绑定,编译阶段确定地址
* 动态多态地址的函数地址晚绑定,运行阶段确定地址
##### **多态的基础概念**
虚函数 virtual
class Animal
{
public:
void speak()
{
cout<<“动物在说话”<<endl;
}
};
//猫类
class Cat:public Animal
{
void speak()
{
cout<<“小猫在说话”<<endl;
}
};
//执行说话的函数
void doSpeak(Animal &animal) //
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
}
int main()
{
void test01();
}
//结果是: 动物在说话 原因是进行了早绑定,在编译阶段就确定了函数的地址
//如果想让猫说话,需要地址晚绑定,因此引入虚函数
//当virtual void speak()=0;时,在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。纯虚函数是一定要被继承的,否则它存在没有任何意义。
class Animal
{
public:
virtual void speak()
{
cout<<“动物在说话”<<endl;
}
};
//猫类
class Cat:public Animal
{
void speak() //重写函数 重写:函数返回值类型 函数名 参数列表 完全相同
{
cout<<“小猫在说话”<<endl;
}
};
//执行说话的函数
void doSpeak(Animal &animal) //
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
}
int main()
{
void test01();
}
##### 动态多态满足条件
* 有继承关系
* 子类重写父类的虚函数, 重写:函数返回值类型 函数名 参数列表 完全相同
动态多态使用
* 父类的指针或者引用 执行子类对象
纯虚函数
#### 文件操作
c++中对文件操作需要包含头文件<fstream>
文件类型分为两种:
* 1.文本文件 -文件以文本的ASCLL码形式存储在计算机中
* 2.二进制文件 -文本以文本的二进制形式存储在计算机中,用户一般不能直接读
操作文件分为三类
* ofstream: 写操作
* ifstream: 读操作
* fstream: 读写操作
##### 1.写文件
1.包含头文件
#include <fstream>
2.创建流对象
ofstream ofs;
3打开文件
ofs.open("文件路径",打开方式);
4.写数据
ofs<<"写入的数据";
5.关闭文件
ofs.close();
### 泛型编程
#### 模板
* 函数模板
* 类模板
**语法**
建立一个通用函数,其函数返回值和形参类型可以不具体制定
template
函数声明或定义
//解释: template --声明创建模板
// typename --表面其后面的符号是一种数据类型,可以用class代替
// T – 通用的数据类型,名字可以替换,通常为大写字母
**函数模板注意事项**
* 自动类型推导,必须推导出一致的数据类型T,才可以使用
* 模板必须要确定出T的数据类型,才可以使用
template
void mySwap(T &a,T &b)
{
T temp =a;
a=b;
b=temp;
}
void test()
{
int a=10;
int b=20;//a,b都是int 所以是一致的数据类型,就能自动推导出int
mySwap(a,b);
cout<<“a=:”<<a<<endl;
cout<<“b=:”<<b<<endl;
}
类模板和函数模板的区别:
* 类模板没有自动类型推导的使用方式
* 类模板在模板参数列表中可以有默认参数
要想存放常量对象的地址,只能使用指向常量的指针
const double pi=3.14;
const double *ptr=π //指针必须加const ,才能修饰常量
指针类型必须与所指对象的类型一致,但是有两个例外
1.允许一个指向常量的指针指向一个非常量对象:
double dval =3.14;
const *cptr=&dval; //正确,但是不能通过cptr改变dval的值
const指针
因为指针是对象,所以常量指针必须初始化
将*放在const之前用以说明指针是一个常量,即不变的是指针本身的值而非指向的那个值
此外,在constexpr声明中如果定义一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:
const int *p=nullptr; //p是一个指向整型常量的指针
constexpr int *p=nullptr; //p是一个指向整型的常量指针,把他置为了顶层const
**四种类型转换**
**static_cast**: 只能用于良性转换,这样的转换风险较低,一般不会发生什么意外
**const_cast**:比较好理解,它用来去掉表达式的 const 修饰或 volatile 修饰。换句话说,const_cast 就是用来将 const/volatile 类型转换为非 const/volatile 类型。
**reinterpret_const** :是“重新解释”的意思,顾名思义,reinterpret_cast 这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,非常简单粗暴,所以风险很高。reinterpret_cast 可以认为是 static_cast 的一种补充,一些 static_cast 不能完成的转换,就可以用 reinterpret_cast 来完成,例如两个具体类型指针之间的转换、int 和指针之间的转换(有些编译器只允许 int 转指针,不允许反过来)。
**dynamic_cast** 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。向上转型是无条件的,不会进行任何检测,所以都能成功;向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。
函数指针指向的是函数而非对象
当我们把函数名作为一个值使用时,该函数自动地转换成指针
void show() const
{
} //在一个类的函数后面加上const后,就表明这个函数是不能改变类的成员变量的(加了mutable修饰的除外)。实际上,也就是对这个this指针加上了const修饰。
静态内存用来保存局部static对象,类static数据成员以及定义在任何函数之外的变量。
栈内存:用来保存定义在函数内的非static对象。
动态内存:c++中通过一对运算符 new,delete,//使用动态内存的一个常见原因是允许多个对象共享相同的状态
shared_ptr //允许多个指针指向同一个对象
unique_ptr //独占所指向的对象
shared_ptr p1;
unique_ptr p2;
share_ptr p3=make_shared(42);
class func()
{
public:
func(); //默认构造函数
func(const func&);//拷贝构造函数
func& operater=(const func&); //赋值运输符
};
值传递 用. s.name
指针传递 -> s->name 指针访问都用->
构造函数调用规则如下
1 如果用户定义有参构造函数,c++不再定义无参构造函数,但会提供默认拷贝构造
2 如果用户定义拷贝构造函数,c++不会再定义其他构造函数
拷贝构造函数什么时候调用?
1当用类的一个对象初始化该类的另一个对象时
2如果函数的形参是类的对象,调用函数时,进行形参和实参结合时
3如果函数的返回值是类的对象,函数执行完成返回调用者时
4需要产生一个临时类对象时
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
类对象作为类成员
this指针的本质是指针常量 指针的指向是不可以修改的
const修饰成员函数
常函数:
成员函数后加const后我们称为这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改
void func() const;
常对象
声明对象前加const称该对象为常对象
常对象只能调用常函数
const Person p;
RAII是Resource Acquisition Is Initialization(wiki上面翻译成 “资源获取就是初始化”)的简称,是C++语言的一种管理资源、避免泄漏的惯用法。利用的就是C++构造的对象最终会被销毁的原则。RAII的做法是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。
RAII是用来管理资源、避免资源泄漏的方法
优先级:()>[]>*
继承中的对象模型
#include
using namespace std;
class Base1
{
public:
int m_a=9867;
protected:
int m_b;
private:
int m_c;
};
class Son: public Base1
{
public:
int m_d;
};
void test01()
{
cout<<“sizeof Son”<<sizeof(Son)<<endl;//结果为16 即父类中的所有非静态成员都被子类继承了,父类中私有成员属性不过是被编译器隐藏了,所以访问不到,但确实被继承下去了
}
int main()
{
test01();
//cout<<s1.m_a<<endl;
//cout<<s1.m_b<<endl;
//cout<<s1.m_c<<endl;
}
如果子类中出现与父类同名的成员函数,子类的成员函数会隐藏掉父类中所有的同名成员函数,s.func();
如果想访问到父类被隐藏的成员函数。需要加作用域 s.Base::func();
菱形继承概念:
两个派生类继承同一个基类
又有另一个类继承了两个派生类
这种继承被称为菱形继承
菱形继承问题:
多继承会有二义性
菱形继承导致数据有两份,资源浪费
1 加作用域区分
sheeptuo st;
st.sheep::age;
st.uo::age;
2利用虚继承来解决
/**加作用域区分/
#include
using namespace std;
class Animal
{
public:
int m_age;
};
class Sheep: public Animal
{
public:
};
class Tuo: public Animal
{
public:
};
class Sheeptuo: public Sheep,public Tuo
{
public:
};
int main()
{
Sheeptuo st;
st.Sheep::m_age=18;
cout<<st.Sheep::m_age<<endl;
return 0;
}
//利用虚继承
#include
using namespace std;
class Animal //虚基类
{
public:
int m_age;
};
class Sheep:virtual public Animal
{
public:
};
class Tuo:virtual public Animal
{
public:
};
class Sheeptuo: public Sheep,public Tuo
{
public:
};
int main()
{
Sheeptuo st;
st.Sheep::m_age=18;
st.Tuo::m_age=28;
cout<<st.Sheep::m_age<<endl;
cout<<st.Tuo::m_age<<endl;
return 0;
}
//虚继承底层剖析:继承的不是数据,是指针,指针通过各自的偏移量找到同一个虚基类数据