牙牙将心的cpp学习之路,03继承与多态

本文详细介绍了C++中的继承概念,包括类的关系、继承的三种方式(public, private, protected)及其影响。讨论了派生类对象构建与基类对象构建的关系,以及如何通过构造函数初始化列表来初始化基类的私有成员。此外,还涉及了C++中的类型强转运算符(static_cast, const_cast, reinterpret_cast, dynamic_cast)和异常处理(exception)的基础知识。" 124285,20858,中国方言地图的数字化探索,"['语言', 'GIS', '数据可视化', '软件开发', '教育']
摘要由CSDN通过智能技术生成

目录

1.类的关系

2.继承概念

3.继承使用

settings.json文件代码

4.派生类对象的构建与基类对象构建的关系

严重性代码说明项目文件行错误C4996 | strcpy

待看代码

代码1

5.类型强转运算符

6.异常处理(exception)


1.类的关系

在类的设计中,往往需要考虑到类与类之间的关系,类的关系往往决定了这个类该怎么去设计/类的部分内容,较目前常见的关系,一般是如下两种:

关联关系:两个类存在着一定的关联,一般是类A是不是属于类B/类A是不是类B的成员类,或者说,类A的某个行为是否需要依赖于类B,所以关联关系一般会划分为3个小类:

依赖关系:类A的某个行为是否需要依赖于类B 例子: 屠夫杀猪,屠夫杀猪,需要一头猪 。      

组合关系:是一种强关联属性的关系,表示一个物体有若干个部件组成,这些部件是不可分割的。

        例子: 计算机主要组成部件(主板,电源,内存,硬盘),对于一台计算机来说是不可拆分的

 聚合关系:是一种强关联属性的关系,表示一个物体有若干个部件组成,这些部件是可以分割的。

        例子: 车子的某些组成部件拆出来是可以单独使用的

以上关系一般可以通过友元/成员类解决这种问题

类的泛化关系:泛化 => 衍生 ,是由一个物体衍生出了其他物体 / 亦或者说某一些物体属于同一个类别。

用户:普通用户,vip用户,超级vip用户.... => 用户的叫法不同,权限也不同,但是他们都是用户

员工:普通员工,项目经理,部门主管,技术总监

动物分类:胎生动物,卵生动物

马科:马 , 骡,驴

泛化关系的表示,在面对对象编程中,往往是以继承的方式是解决的

继承 =》 见下文

2.继承概念

继承的基本概念,和我们生活中的继承是差不多的,都是指从某个实体中得到了一些东西(属性/行为),而类的继承一般是指,新有的类从已有的类那里得到属性和行为的一种方式,除了从已有类中获取数据之外,还可以自己去新建一些行为+属性(青出于蓝,而胜于蓝),在类的继承关系中,新的类一般叫做派生类/子类(derived class),而已有的类一般叫做 父类/基类(base class)/超类(古老版本叫法,你只会在软考上看到这种东西),具体见图:

 派生类从基类中继承属性+行为,继承之后也可以自己定义新的属性+行为,其结构一般如下

3.继承使用

先了解下基本的语法:

class 派生类名:继承方式 基类名,继承方式 基类名1 ...
{
    约束:
        行为/属性
    约束:
        行为/属性
};

继承方式:和约束是一样的 public,private,protected,这三种继承方式会改变从基类继承过来数据的访问权限。
具体如下表

继承方式基类private基类public基类protected
public派生类不可访问变成派生类的public变成派生类的protected
private派生类不可访问变成派生类的private变成派生类的private
protected派生类不可访问变成派生类的protected变成派生类的protected

怎么记呢? 三种权限从高到底 public - protected - private, 继承过程中,低权限继承方式会把高权限内容下降到低权限位置,低权限内容不改变 (这就像一个门槛,高于门槛的要削减,低于门槛的直接原属性继承)

示例代码: usr.cpp

看看继承方式对数据的影响

基类的私有成员访问,只能通过基类的函数去访问(你如果通过指针强行操作,我也没辙)

//usr.cpp代码文件

#include<iostream>
using namespace std;

//用户类
class usr
{
private:
	char *name; //8         0x1008
	int age; //4            0x1010

public:
	void work();
	int m; //测试用  //4    0x1000

protected://类外不可访问,但是类内可访问
	int test;//4            0x1014
};
void usr::work()
{
	age = 100;
	cout << "working" << endl;
}

//普通用户类
class normal_usr :public usr
{
public:
	void listen_music();
private:
	float money;//4    0x1018
};
void normal_usr::listen_music()
{
	//age = 100;//基类的私有成员,派生类不可以直接访问
	test = 100;//访问基类继承过来的protected成员
	cout << "listen_music" << endl;
}

//测试方式: 继承过来的属性是public,使用类外访问方式
//protected:类内成员访问 =》 可访问
//基类的private:类内成员访问 =》 不可访问

int main()
{
	normal_usr nu;
	nu.m = 100;
	nu.work();//public继承方式下,基类public成员在派生类中依旧是public
	
	cout << sizeof(nu) << endl;
	//从这句话中,我们测出了,其实就算基类成员是私有的,但是还是会继承
	//既然继承了,那么肯定会有访问的方式
}


settings.json文件代码

{
//settings.json文件代码
    "files.associations": {
        "iostream": "cpp",
        "array": "cpp",
        "atomic": "cpp",
        "*.tcc": "cpp",
        "cctype": "cpp",
        "clocale": "cpp",
        "cmath": "cpp",
        "cstdarg": "cpp",
        "cstddef": "cpp",
        "cstdint": "cpp",
        "cstdio": "cpp",
        "cstdlib": "cpp",
        "cstring": "cpp",
        "cwchar": "cpp",
        "cwctype": "cpp",
        "deque": "cpp",
        "unordered_map": "cpp",
        "vector": "cpp",
        "exception": "cpp",
        "algorithm": "cpp",
        "functional": "cpp",
        "iterator": "cpp",
        "memory": "cpp",
        "memory_resource": "cpp",
        "numeric": "cpp",
        "optional": "cpp",
        "random": "cpp",
        "string": "cpp",
        "string_view": "cpp",
        "system_error": "cpp",
        "tuple": "cpp",
        "type_traits": "cpp",
        "utility": "cpp",
        "fstream": "cpp",
        "initializer_list": "cpp",
        "iosfwd": "cpp",
        "istream": "cpp",
        "limits": "cpp",
        "new": "cpp",
        "ostream": "cpp",
        "sstream": "cpp",
        "stdexcept": "cpp",
        "streambuf": "cpp",
        "typeinfo": "cpp"
    }
}

4.派生类对象的构建与基类对象构建的关系

基类拥有构造函数的 => 用于在构建基类对象时,初始化基类成员的。

派生类也会有构造函数 => 用于在构建派生类对象时,初始化成员的。

问题是,我基类的私有成员是继承过来的,但是我基类的私有成员,我派生类无法直接访问,那么基类的私有成员怎么在派生类中进行初始化呢?

=> 构造函数初始化列表

派生类对象构建,我们需要在构造函数上添加构造函数初始化列表,以调用基类构造函数初始化基类对象。

示例代码:inherit.cpp

严重性代码说明项目文件行错误C4996 | strcpy

C4996 ‘strcpy’: This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

出现这个错误,直接在头文件中添加以下这行代码即可(注意(disable:4996)也是需要添加的,不要只复制#pragma warning,下面的这行代码是一个整体,需要都复制):

#pragma warning(disable:4996)


//inherit.cpp代码文件
#include<iostream>
#include<cstring>

// 
#pragma warning(disable:4996)

// ///
using namespace std;

//用户类
class usr
{
private:
	char *name; //8         0x1008
	int age; //4            0x1010

public:
	void work();
	usr(const char *ptr_name, int vage);
	~usr();
};
usr::usr(const char *ptr_name, int vage)
{
	name = new char[strlen(ptr_name) + 1];
	strcpy(name, ptr_name);
	age = vage;
	cout << "usr(char *,int)" << endl;
}
usr::~usr()
{
	delete []name;
	cout << "~usr()" << endl;
}
void usr::work()
{
	cout << "working" << endl;
}


//普通用户类
class normal_usr :public usr
{
private:
	float money;
public:
	void listen_music();
	//因为基类的构造函数调用需要通过派生类构造函数,
	//所以派生类构造函数参数列表需要包含基类构造函数参数
	normal_usr(const char *ptr_name, int vage, float vmoney);
	~normal_usr();
	void work(int);
};
//派生类构造函数初始化列表,通过写基类类名调用基类构造函数
normal_usr::normal_usr(const char *ptr_name, int vage, float vmoney) :usr(ptr_name, vage), money(vmoney)
{
	cout << "normal_usr()" << endl;
}
normal_usr::~normal_usr()
{
	cout << "~normal_usr()" << endl;
}
void normal_usr::listen_music()
{
	cout << "listen_music" << endl;
}
void normal_usr::work(int a)//类内有同名函数,那么基类函数会被覆盖
{//因为作用域不同,函数重载条件不成立,只要是同名,直接覆盖了,无关参数列表
	cout << "qiaodaima" << endl;
}


//继承关系中,
// 构造顺序: 基类先构造,派生类后构造
// 析构顺序: 派生类先析构,基类后析构
int main()
{
	normal_usr nu("xwy", 22, 4500.0);
	nu.work(10);
	//nu.work();
	nu.usr::work();//如果我想使用基类的,那么只需要写个作用域就行
}

/*
usr(char *, int)"
"normal_usr()"

qiaodaima"

"working" 

"~normal_usr()"
"~usr()" 
*/

练习:
    1.居民 - 成年人 - 党员
        居民: 身份证、姓名、性别、出生日期
        成年人:最高学历、职业
        党员: 入党时间
      建立以上三个类:  成年人继承居民 ,党员继承成年人 
      构造函数/析构函数完成
      再写个show/运算符重载>> 输出内容
      
     2.汽车 - 小车 - 卡车
         汽车:车轮个数,车重(皆为保护属性)
         小车: 载人数
         卡车: 载人数 ,载重量
         小车,卡车皆为汽车的私有派生
      构造函数/析构函数完成
      再写个show/运算符重载>> 输出内容
      
     3.动物类 - 猫 - 狗
     动物类: 行为: 发声
     猫: 发声 喵喵喵
     狗:发声 汪汪汪
     猫,狗皆为动物类继承

居民代码文件:

//居民代码文件
#include <iostream>
#include <cstring>
// //
#pragma warning(disable:4996)

// /
using namespace std;

/*
练习:
1.居民一成年人一党员
居民:身份证、 姓名、性别、出生日期
成年人:最高学历、职业
党员:入党时间
建立以上三个类:成年人继承居民,党员继承成年人。
构造函数/析构函数完成
再写个show/运算符重载>>输出内容
*/
class resident
{
private:
	char *num_id;
	char *name;
	char *sex;
	char *born_data;
public:
	resident(const char *id, const char *vn, const char *vs, const char *vb);
	~resident();
	void show();
};

class adult : public resident
{
private:
	char *highest_edu;
	char *profess;
public:
	adult(const char *id, const char *vn, const char *vs, const char *vb, const char *vh, const char *vp);
	~adult();
	void show();
};

class party_mem : public adult
{
private:
	char *join_party_time;
public:
	party_mem(const char *id, const char *vn, const char *vs, const char *vb, const char *vh, const char *vp, const char *vj);
	~party_mem();
	void show();
};


resident::resident(const char *id, const char *vn, const char *vs, const char *vb)
{
	num_id = new char[strlen(id) + 1];
	strcpy(num_id, id);

	name = new char[strlen(vn) + 1];
	strcpy(name, vn);

	sex = new char[strlen(vs) + 1];
	strcpy(sex, vs);

	born_data = new char[strlen(vb) + 1];
	strcpy(born_data, vb);

	cout << "resident(char *,char *,char *,char *)" << endl;
}
resident::~resident()
{
	delete []num_id;
	delete []name;
	delete []sex;
	delete []born_data;
	cout << "~resident()" << endl;
}

adult::adult(const char *id, const char *vn, const char *vs, const char *vb, const char *vh, const char *vp) :resident(id, vn, vs, vb)
{
	highest_edu = new char[strlen(vh) + 1];
	profess = new char[strlen(vp) + 1];
	strcpy(highest_edu, vh);
	strcpy(profess, vp);
	//strcpy_s(target, length, source);
	cout << "adult(char *,char *,char *,char *,char *,char *)" << endl;
}
adult::~adult()
{
	delete []highest_edu;
	delete []profess;
	cout << "~adult()" << endl;
}

party_mem::party_mem(const char *id, const char *vn, const char *vs, const char *vb, const char *vh, const char *vp, const char *vj) :adult(id, vn, vs, vb, vh, vp)
{
	join_party_time = new char[strlen(vj) + 1];
	strcpy(join_party_time, vj);
	cout << "party_mem(char *,char *,char *,char *,char *,char *,char *)" << endl;
}
party_mem::~party_mem()
{
	delete []join_party_time;
	cout << "~party_mem()" << endl;
}


void resident::show()
{
	cout << "身份证:" << num_id << endl << "姓名:" << name << endl << "性别:" << sex << endl << "出生日期:" << born_data << endl;
}

void adult::show()
{
	resident::show();
	cout << "最高学历:" << highest_edu << endl << "职业:" << profess << endl;
}

void party_mem::show()
{
	adult::show();
	cout << "人党时间:" << join_party_time << endl;
}



int main()
{
	//你要写个重载。。。 cin >>  不建议写cin,建议写点局部变量,直接复制,更好
	party_mem p("666666202203224444", "小尾羊", "男", "2022/03/22", "学历A级", "特务A级", "不是党员");
	p.show();
}

待看代码

代码1

//Car.cpp代码文件

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;

class Automobile
{
protected:
    int wheel;
    int aweight;
};

class Car:private Automobile
{
private:
    int cpeoples;
public:
    Car(int cw,int ca,int ic);
    friend ostream & operator<<(ostream &out,Car& a);
    ~Car();
};
Car::~Car(){}
Car::Car(int cw,int ca,int ic)
{
    cpeoples = ic;
    wheel = cw;
    aweight = ca;
}

ostream & operator<<(ostream &out,Car& a)
{
    cout<<"小车   "<<"车轮数量:"<<a.wheel<<"车重:"<<a.aweight<<"载人数量:"<<a.cpeoples<<"\n";
}


class Truck:private Automobile
{
private:
    int tpeople;
    int weight;//如果基类不需要构造对象 => 接口类 ,接口类的存在一般为接下来继承这个接口类的
    //派生类提前定义一些属性 / 空的函数(虚函数),要求派生类使用这些内容
public:
    Truck(int wc,int ac,int ti,int we);
    friend ostream & operator<<(ostream &out,Truck& a);
    ~Truck();
};
Truck::~Truck(){};
Truck::Truck(int wc,int ac,int ti,int we)//基类的构造函数任然调用了
{//但是调用的是,编译器默认生成的无参构造函数,如果基类的构造函数是无参的,那么不需要显示的调用
    wheel = wc;
    aweight = ac;
    tpeople = ti;
    weight = we;
}
ostream & operator<<(ostream &out,Truck& a)
{
    cout<<"卡车   "<<"车轮数量:"<<a.wheel<<"车重:"<<a.aweight<<"载人数量:"<<a.tpeople<<"载重量:"<<a.weight<<"\n";
}

int main()
{
    Car a(1,1,1);
    Truck b(1,1,1,1);
    cout<<a<<"\n";
    cout<<b<<"\n";
    return 0;
}

5.类型强转运算符

在C语言中,我们类型强转的方式都是 (新类型)对象/表达式,但是C语言强转类型的方式,在C++看来存在一些问题

1) 太简单粗暴了,可以强转任意类型,不在乎强转之后数据是否会丢失什么的,这在C++看来是一个危险的行为 2) 使用强转运算符之后,不好定位代码中那些位置使用了强转类型操作 (类型) =》(int *) 不好通过ctrl+f查找

C++中为了解决这种问题,就自己定义了4种类型强转运算符,以替代C强转运算符的使用

1) static_cast

```
一般表示静态转换 => 类型转换,在编译期间就能完成
限制:只能使用与基本类型转换(int,char,float,double,long),能用于有继承关系类对象/指针之间的转换(派生类能转基类,基类不可转派生类(包含)),非只读 -> 只读

示例代码: cast.cpp
``

2) const_cast

```
去除只读属性 => 把只读数据变成非只读的,但是作用对象只能用于指针/引用
```

3) reinterpret_cast

```
重定义类型转换
    规则:在不改变数据长度的情况下,重新解释该数据的意思
    int * => char *
    int * => int //err  数据长度改变,不行
```

4) dynamic_cast

```
动态类型转换:用于有继承关系的类指针之间的转换
```

 //cast.cpp代码文件

//cast.cpp代码文件

#include<iostream>
using namespace std;

class base
{
    public:
    virtual void func();
};
void base::func()
{
    cout << "base" << endl;
}
class derived:public base
{
    public:
    virtual void func();
};
void derived::func()
{
    cout << "derived" << endl;
}

/*
    静态转换示例
    格式:
    static_cast<强转的类型>(表达式/对象);
*/
void static_cast_exam()
{
    int a = 10;
    char c = static_cast<char>(a); //这种操作都有隐式类型强转
    base b;
    derived d;
    //d = static_cast<derived>(b);//基类转派生类是不行的
    b = static_cast<base>(d);//这种操作都有隐式类型强转,派生类对象可以直接赋值基类(包含关系)
}
/*
    const_cast转换示例
    格式:
    const_cast<强转的类型>(表达式/对象);
*/
void const_cast_exam()
{
    int a = 10;
    const int &b = a;
    const int *p = &b;

    int &c = const_cast<int &>(b);//int &c = b 不行,const赋值 非const => 数据安全性丢失
    int *q = const_cast<int *>(p);//int *q = p 不行,const赋值 非const => 数据安全性丢失
    const int a2 = 10;
    //int &b = const_cast<int>(a2);//const 变量不能赋值给 非const 变量
    //const_cast<int> 是不可操作的,未定义的操作
}
/*
    reinterpret_cast转换示例
    格式:
    reinterpret_cast<强转的类型>(表达式/对象);
*/
void reinterpret_cast_exam()
{
    base *a = new derived();//以派生类对象初始化一个基类对象
    derived *b = reinterpret_cast<derived*>(a);//reinterpret_cast 可以重新解释指针,但是这个操作的后果需要你自己承担

    long c = reinterpret_cast<long>(a);
    //long d = reinterpret_cast<int>(a);//在不改变数据长度的情况下,重新解释该数据的意思
    //不能把a的长度改变  8 - 4 是不行的
    int a1 = 100;
    b = reinterpret_cast<derived*>(a1);//不会检测短数据变长数据
}

/*
   dynamic_cast转换示例
    格式:
    dynamic_cast<强转的类型>(表达式/对象);
*/
void dynamic_cast_exam()
{
    base *b = new derived();
    b->func(); //多态概念,基类指针指向派生对象,会调用派生类的虚函数

    derived* d = dynamic_cast<derived*>(b);
    d->func(); //派生类对象调用自己的虚函数
}

int main()
{
    //static_cast_exam();
    dynamic_cast_exam();
}


6.异常处理(exception)

C++中,错误一般分为两种,编译时错误(语法问题,文件路径问题)和运算时错误(逻辑问题),C++,异常指的是程序运行过程中,由于系统条件,代码中某些操作不当使得程序崩溃,而产生的问题(run time err => 运行是错误),在C语言中,一般当程序运行时出错,会提供一个错误码和一段对应的错误信息给你,然后就提前结束程序了,但是在C++中,认为程序的运行在某个地方出错,不应该直接一次性结束程序,应该给程序一次机会,让其能够继续运行,而这种操作就是C++中异常处理机制

异常处理相关使用非常简单,分3步:

1) 使用throw 抛出异常

​​​​​​​

```
float a = 100;
float b = 0;

if(b == 0)//操作数不合理
{
    //抛出异常,告诉系统,当前程序出问题了,"这段代码"马上结束运行
    //格式 :throw 临时对象内容/系统预定义对象内容;
    throw "heheh";
}
else
{
    a/b 操作
}
```

2.使用catch 接收/捕捉异常信息

```
格式:
try
{
    //包含了调用throw语句的代码段,可以是函数调用
}catch(throw抛出数据类型 对象名字)
{
    //3.对异常进行处理
}catch(throw抛出数据类型 对象名字)
{
    //3.处理异常
}
示例代码:
exception.cpp

C++标准提供一些错误信息类以描述各种错误信息,常用的
runtime_error    运行时错误 => 值不对劲
out_of_range  逻辑错误越界
bad_alloc   内存分配失败  new => 返回nullptr
...
在这些错误信息类中会有一个共有的成员函数 what,用于输出一个字符内容 =》 对应的错误信息
cplusplus.com
creference.com =>有中文版

如果你写的函数,你觉得绝对不会有问题,那么你可以在这个函数的声明后面添加一个 noexecpt 表示这个函数是个无异常函数
```

//exception.cpp代码文件

//exception.cpp代码文件

#include<iostream>
#include<cstdio>
using namespace std;
int sum(int a,int b) noexcept;

int sum(int a,int b)
{
    return a+b;
}


void cout_str(char * p)
{
    if(p == nullptr)
    {
        throw runtime_error("hehehe");//抛出异常
        //当我们使用throw抛出异常之后,当前代码段内会直接结束,在这个代码段内构造的对象会被自动的析构
        //如果异常抛出的类型是不存在的,那么会报错,且程序崩溃
    } 
    printf("%s\n",p);//上面抛出异常后,这个代码就不会运行了
}

int main()
{
    char *p = nullptr;
    try //异常机制的搭配使用/固定结构
    {
        cout_str(p);
    }
    catch(const char *info)//catch用于捕捉异常,根据返回的数据类型去捕捉异常
    {
        cerr << info << '\n';//异常处理语句
        p = new char[100];
        cin >> p;
        cout_str(p);//修正操作,使得cout_str能够再次执行
    }
    catch(const int a)
    {
        cerr << a << '\n';
    }
    catch(runtime_error &e)
    {
        cerr << e.what() <<endl;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

牙牙将心

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

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

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

打赏作者

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

抵扣说明:

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

余额充值