c++

指针
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;
}
//虚继承底层剖析:继承的不是数据,是指针,指针通过各自的偏移量找到同一个虚基类数据


   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值