第九章 C++结构体的讲解
1.结构体的介绍
在一个组合项中包含若干个类型不同的数据项,C++允许用户指定这样一种数据结构,称为结构体。
- 结构体描述
一个优秀的结构体应该具备这样的特征:
它可以任意指定数据类型;它可以随意嵌套其他结构体类型数据;它可以满足我们对一个事物的完整描述。
比如描述一个人的信息:
- 定义
关键字:struct
2.结构体的声明
-声明
一个结构体类型的一般形式:
- 声明一个人
struct Person{
char name[20];
int age;
double height;
};
Person为自定义结构体类型名称。此结构体包含了三个不同类型的成员变量。
- 结构体变量
声明完结构体类型之后,会产生一个新的类型。比如 Person,可以像操作基本数据类型一项,给自定义的结构体类型定义变量。比如:Person jack;
则jack拥有三个属性,分别为姓名、年龄、身高。
#include<iostream>
using namespace std;
int main(void){
struct Person{
char name[20];
int age;
double height;
};
Person jack={"jack",25,165.5};
return 0;
}
- 结构体初始化
1.直接初始化Person jerry={"manrry",18,165,5};
2.单独初始化 新增.运算符 .运算符用来引用成员变量。
Person jack;
jack.age=23;
jack.height=178.28;
strcpy(jack.name,"jack");
由于数组不能直接赋值,所以可以采用字符串函数方式赋值。
3.结构体指针
- 声明结构体
struct Person{
char name[20];
int age;
double height;
}jerry={"marry",35,175.5};
//定义结构体指针
Person* p_jerry=&jerry;
- 结构体指针->
->运算符用于结构体指针输出成员变量数据的。例如jack->name;
输出jack结构体的name属性。
#include<iostream>
#include<cstring>
using namespace std;
int main(void){
struct Person{
char name[20];
int age;
double height;
}jack={"marry",25,165.5};
//结构体指针
Person* p_jack=&jack;
//->运算符用在指针结构体上面
cout<<p_jack->name<<endl;
p_jack->age=20;
p_jack->height=175.23;
cout<<p_jack->age<<endl;
return 0;
}
4.结构体嵌套定义结构体
#include<iostream>
#include<cstring>
using namespace std;
int main(void){
struct Student{
char name[20];
int age;
double height;
//嵌套结构体
struct Work{
char detail[30];
int time;
}work;
};
Student xiaoli={"xiaoli",26,171.2,{"技术",8}};
cout<<"xiaoli:name="<<xiaoli.name<<",age="<<xiaoli.age<<",height="<<xiaoli.height<<endl;
cout<<",work-detail="<<xiaoli.work.detail<<",work-time="<<xiaoli.work.time<<endl;
return 0;
}
5.结构体使用之前定义结构体
#include<iostream>
#include<cstring>
using namespace std;
int main(void){
struct Teach{
char course[20];
int code;
};
struct Teacher{
char name[20];
Teach english;
Teach math;
};
Teacher wang={"wang",{"english",1001},{"math",1002}};
//输出老师
Teacher* p_wang=&wang;
cout<<"wang:name="<<p_wang->name<<",course-english-name="<<p_wang->english.course<<endl;
cout<<"course-english-code="<<p_wang->english.code<<endl;
cout<<"course-math-name="<<p_wang->math.course<<",course-math-code="<<p_wang->math.code<<endl;
return 0;
}
6.结构体的大小
其实字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:
1.结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2.结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字符;
3.结构体的总大小为结构体最宽基本类型成员(子结构体不是基本类型)大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字符。
- 字节对齐
计算机存储系统中以Byte为单位存储数据,不同数据类型所占的空间不同。计算机为了快捷的读写数据,默认情况下将数据存放在某个地址的起始位置,如:整型数据(int)默认存储在地址能被4整除的起始位置,字符数据(char)可以存放在任何地址位置(被1整除),短整除(short)数据存储在地址能被2整除的起始位置。这就是默认字节对齐方式。
#include<iostream>
using namespace std;
int main(void){
struct student{
char name[5];
int age;
short sex;
};
cout<<"student:"<<sizeof(student)<<endl;//student:16
return 0;
}
- 改变顺序
#include<iostream>
using namespace std;
int main(void){
struct student1{
int age;
char name[5];
short sex;
};
cout<<"student1:"<<sizeof(student1)<<endl;//student1:12
return 0;
}
7.结构体作函数参数
- 结构体参数
利用结构体作为函数参数可以传入不同类型参数,简化传入参数个数,使代码看起来更加简洁。
#include<iostream>
using namespace std;
struct BreakFast{
char name[10];
int time;
double water;
};
void showBreakFast(char person[],BreakFast breakFast);
int main(void){
char name1[]="smith";
BreakFast breakFast={"apple",7,56.33};
showBreakFast(name1,breakFast);
return 0;
}
void showBreakFast(char person[],BreakFast breakFast){
cout<<"person:"<<person<<"准备吃早餐了;"<<endl;
cout<<"早餐:"<<breakFast.name<<endl;
cout<<"时间:"<<breakFast.time<<endl;
cout<<"水分:"<<breakFast.water<<endl;
}
结果:
8.结构体指针作函数参数
- 结构体参数
使用结构体指针 作函数参数传入的是地址,减少了时间和空间上的开销,能够提高程序工作效率。
#include<iostream>
using namespace std;
struct Lunch{
char name[10];
int time;
double water;
};
void showLunch(char* person,Lunch* lunch);
int main(void){
char name1[]="lily";
Lunch lunch={"盖浇饭",12,80.33};
showLunch(name1,&lunch);
return 0;
}
void showLunch(char* person,Lunch* lunch){
cout<<"person:"<<person<<"准备吃午餐了;"<<endl;
cout<<"午餐:"<<lunch->name<<endl;
cout<<"时间:"<<lunch->time<<endl;
cout<<"水分:"<<lunch->water<<endl;
}
结果:
9.结构体数组
#include<iostream>
using namespace std;
int main(void){
//定义动物结构体
struct Animal{
char name[20];
int code;
short age;
};
//对动物数组初始化
Animal animal[5]={
{"老虎",1001,12},
{"猴子",1002,10},
{"大象",1003,8},
{"斑马",1004,6},
{"兔子",1005,4},
};
cout<<"动物园情况报告:"<<endl;
for(int i=0;i<5;i++){
cout<<"编号:"<<animal[i].code<<",名称:"<<animal[i].name<<",寿命:"<<animal[i].age<<endl;
}
return 0;
}
结果:
10.指针访问结构体数组
#include<iostream>
using namespace std;
int main(void){
//定义动物结构体
struct Animal{
char name[20];
int code;
short age;
};
//对动物数组初始化
Animal animal[5]={
{"老虎",1001,12},
{"猴子",1002,10},
{"大象",1003,8},
{"斑马",1004,6},
{"兔子",1005,4},
};
//输出结构体数组
cout<<"动物园情况报告:"<<endl;
for(int i=0;i<5;i++){
cout<<"编号:"<<animal[i].code<<",名称:"<<animal[i].name<<",寿命:"<<animal[i].age<<endl;
}
cout<<"================================"<<endl;
//指针访问结构体数组
Animal* p_animal=animal;
for(int i=0;i<5;i++,p_animal++){
cout<<"编号:"<<p_animal->code<<",名称:"<<p_animal->name<<",寿命:"<<p_animal->age<<endl;
}
return 0;
}
- 练习
#include<iostream>
using namespace std;
//定义动物结构体
struct Animal{
char name[20];
int code;
short age;
};
//对动物数组初始化
Animal animal[5]={
{"老虎",1001,12},
{"猴子",1002,10},
{"大象",1003,8},
{"斑马",1004,6},
{"兔子",1005,4},
};
void showAnimals(Animal* animal,int n);
int main(void){
showAnimals(animal,5);
return 0;
}
void showAnimals(Animal* animal,int n){
cout<<"动物园情况报告:"<<endl;
//指针访问结构体数组
for(int i=0;i<n;i++,animal++){
cout<<"编号:"<<animal->code<<",名称:"<<animal->name<<",寿命:"<<animal->age<<endl;
}
}
结果:
11.结构体练习
struct Worker{
char name[10];
int age;
double height;
//定义工作结构体
struct{
char workDetail[20];
int time;
//定义工作场所结构体
struct{
char address[50];
int postCode;
}workPlace;
}work;
struct{
char name[10];
char style[10];
}hoby;
};
#include<iostream>
using namespace std;
//练习二
int main(void){
struct Course{
char name[10];
int code;
int score;
};
struct Student{
char name[10];
int age;
Course chinese;
Course english;
Course math;
};
Student stu1={"lily",23,{"语文",1001,89},{"英语",1002,98},{"数学",1003,88}};
int sumScore;
int scoreAver;
sumScore=stu1.chinese.score+stu1.english.score+stu1.math.score;
scoreAver=sumScore/3;
cout<<stu1.name<<"的总成绩:"<<sumScore<<",平均成绩:"<<scoreAver<<endl;
return 0;
}
12.结构体总结
第十章 C++共同体枚举
1.共同体介绍
- 共同体定义
将不同数据类型的数据项组合在一起,他们在内存中占用首地址相同的一段存储单元,每个瞬间只会有一种数据类型有效。
- 关键字
声明一个共同体也可以说是联合体数据结构。 - 内存存储
特点:- 1.同一个时刻只会存在一种数据类型的数据
- 2.只有最后一次被赋值的成员才有效
- 3.各个成员变量的地址都一样
- 4.不能在定义共同体时候初始化
- 5.不能将共同体变量名作为函数参数
2.共同体操作
- 共同体语法
#include<iostream>
using namespace std;
int main(void){
//共同体的定义
union Single{
int a;
char b;
double c;
};
//共同体初始化
Single single;
single.a=5;
cout<<"共同体Single:a="<<single.a<<",b="<<single.b<<",c="<<single.c<<endl;
single.b='a';
cout<<"共同体Single:a="<<single.a<<",b="<<single.b<<",c="<<single.c<<endl;
single.c=23.32;
//输出共同体
cout<<"共同体Single:a="<<single.a<<",b="<<single.b<<",c="<<single.c<<endl;
return 0;
}
结果:
- 共同体大小
共同体大小是最长成员的大小
例如:cout<<"single大小:"<<sizeof(single)<<endl;
结果:8
3.枚举的介绍
-
枚举的定义
如果一个变量只有几种可能的值,可以定义为枚举类型。所谓枚举就是将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。 -
枚举语法
关键字enum
-
星期枚举
enum week{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};
4.枚举的操作
#include<iostream>
using namespace std;
void showEnums();
enum week{Monday=14,Tuesday=20,Wednesday,Thursday,Friday,Saturday=9,Sunday};
int main(void){
showEnums();
return 0;
}
void showEnums(){
cout<<"Monday="<<Monday<<endl;
cout<<"Tuesday="<<Tuesday<<endl;
cout<<"Wednesday="<<Wednesday<<endl;
cout<<"Thursday="<<Thursday<<endl;
cout<<"Friday="<<Friday<<endl;
cout<<"Saturday="<<Saturday<<endl;
cout<<"Sunday="<<Sunday<<endl;
}
5.typedef 声明新类型
- typedef
typedef int myint;
myint hello=3;
- 练习
#include<iostream>
using namespace std;
int main(void){
typedef int myint;
myint a=23;
cout<<a<<endl;
//声明结构体
struct Calender{
int year;
int month;
int day;
};
//重命名日期
typedef Calender Date;
Date today={2020,8,5};
cout<<"today:"<<today.year<<"-"<<today.month<<"-"<<today.day<<endl;
//声明共同体
union Water{
int a;
int b;
double c;
};
//重命名共同体
typedef Water Quid;
Quid quid;
quid.a=23;
cout<<"quid.a="<<quid.a<<endl;
//声明枚举
enum Week{
Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};
//重命名枚举
typedef Week WorkDay;
WorkDay wd=Sunday;
cout<<"enum:"<<wd <<endl;
return 0;
}
6.动态分配和回收存储空间
C++使用new和delete来分配和回收内存空间
关键字:new delete
-
new
语法:new 类型[初值] 作用:分配一个存储空间并,返回相应的地址即指针。
注意:用new分配数组空间时候不能指定初值。如果由于内存不足等原因无法正常分配空间,那么new会返回一个空指针NULL,可以根据该指针判断是否成功分配空间。 -
delete删除空间
语法:
//针对变量
delete 指针变量
//针对数组
delete [] 指针变量
#include<iostream>
using namespace std;
int main(void){
int* p=new int(100);
cout<<"p="<<p<<",p中的内容是:"<<*p<<endl;
char* p1=new char[5];
p1[3]='s';
cout<<"p1[3]="<<p1[3]<<endl;
delete p;
//cout<<"p="<<p<<",p中的内容是:"<<*p<<endl;
delete [] p1;
//cout<<"p1[3]="<<p1[3]<<endl;
}
7.练习题之共同体
1.请自定义一个共同体数据类型描述共同体特征?
#include<iostream>
using namespace std;
int main(void){
union Myunion{
int a;
float b;
bool c;
};
Myunion myUnion;
myUnion.a=6;
cout<<"myUnion.a="<<myUnion.a<<endl;
return 0;
}
2.设计一种数据结构,老王要给女朋友带外卖,可以带肯德基、盖浇饭、煲仔饭、重庆小面但是只能带一种,并输出老王给女友带了什么饭?
#include<iostream>
#include<cstring>
using namespace std;
int main(void){
union FoodOfWang{
char kfc[20];
char gif[20];
char bzf[20];
char cqxm[20];
}foodOfWang;
int fow=3;
switch(fow){
case 1:
strcpy(foodOfWang.kfc,"肯德基");
cout<<"老王带的饭是:"<<foodOfWang.kfc<<endl;
break;
case 2:
strcpy(foodOfWang.gif,"盖浇饭");
cout<<"老王带的饭是:"<<foodOfWang.gif<<endl;
break;
case 3:
strcpy(foodOfWang.bzf,"煲仔饭");
cout<<"老王带的饭是:"<<foodOfWang.bzf<<endl;
break;
case 4:
strcpy(foodOfWang.cqxm,"重庆小面");
cout<<"老王带的饭是:"<<foodOfWang.cqxm<<endl;
break;
}
return 0;
}
3.设计一个结构体,要求有姓名、年龄、身高字段以及类型,类型用来表示最后一个字段是老师信息还是学生信息,两者占用同一段空间。
#include<iostream>
#include<cstring>
using namespace std;
int main(void){
struct Person{
char name[20];
int age;
double height;
short type;
union {
struct{
char name[20];
int age;
char study[20];
}student;
struct{
char name[20];
int code;
char work[20];
}teacher;
}remark;
};
Person lily;
lily.type=1;//2
lily.age=23;
lily.height=176.3;
if(lily.type==1){
strcpy(lily.remark.student.name,"Lily");
strcpy(lily.remark.student.study,"良好");
}
else{
strcpy(lily.remark.teacher.name,"Lily");
strcpy(lily.remark.teacher.work,"语文老师");
lily.remark.teacher.code=1001;
}
cout<<"type="<<lily.type<<",age="<<lily.age<<",height="<<lily.height<<endl;
if(lily.type==1){
cout<<"学生信息:name="<<lily.remark.student.name<<",study="<<lily.remark.student.study<<endl;
}
else{
cout<<"老师信息:name="<<lily.remark.teacher.name<<",work="<<lily.remark.teacher.work<<",code="<<lily.remark.teacher.code<<endl;
}
return 0;
}
结果:
8.练习题之枚举
1.小王开了个服装设计工作室,暂时只有四款衣服,请用枚举给小王的衣服描述,并且输出各自的值?
#include<iostream>
#include<cstring>
using namespace std;
int main(void){
enum Clothes{
coat=1,shirt,suit,sports,wear
};
cout<<"小王的衣服:coat="<<coat<<",shirt="<<shirt<<",suit="<<suit<<",sports="<<sports<<",wear="<<wear;
return 0;
}
2.一年四季
#include<iostream>
#include<cstring>
using namespace std;
int main(void){
enum Seaons{
spring,summer,fall,winter
};
Seaons seaon=summer;
if (seaon==1){
cout<<"季节是:"<<"夏天"<<endl;
}
return 0;
}
3.值班
#include<iostream>
using namespace std;
enum Week{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};
void showEnums(Week wk);
int main(void){
Week week=Friday;
showEnums(week);
return 0;
}
void showEnums(Week wk){
switch(wk){
case Monday:
cout<<"周一小明值班"<<endl;
break;
case Tuesday:
cout<<"周二小王值班"<<endl;
break;
case Wednesday:
cout<<"周三小周值班"<<endl;
break;
case Thursday:
cout<<"周四小康值班"<<endl;
break;
case Friday:
cout<<"周五小杨值班"<<endl;
break;
case Saturday:
cout<<"周六小张值班"<<endl;
break;
case Sunday:
cout<<"周天小李值班"<<endl;
default:
break;
}
}
9.练习题之分配回收空间
1.开辟空间存储结构体并回收空间
#include<iostream>
#include<cstring>
using namespace std;
int main(void){
struct Teacher{
string name;
int age;
double height;
string job;
};
//分配结构体变量空间
Teacher* p=new Teacher({"wangLaoShi",23,167.2,"语文老师"});
cout<<"老师,name="<<p->name<<",age="<<p->age<<",height="<<p->height<<",job="<<p->job<<endl;
//释放空间
delete p;
//cout<<"老师,name="<<p->name<<",age="<<p->age<<",height="<<p->height<<",job="<<p->job<<endl;
return 0;
}
2.开辟空间存储数组并回收空间
#include<iostream>
#include<cstring>
using namespace std;
int main(void){
//分配数组存储空间
int* p=new int[5];
p[3]=99;
cout<<"p[3]="<<p[3]<<endl;
//释放数组空间
delete[] p;
cout<<"p[3]="<<p[3]<<endl;
return 0;
}
第十一章 面向对象编程
1.面向过程的程序设计总结
-
思想
-> 程序必须告诉计算机应当具体怎么做,也即是要给出计算机全部操作的过程,执行完这个过程也就是实现了问题的求解。
-> 基于过程的程序设计反映的是事物在计算机中的实现方式,而不是事物在现实生活中的实现方式。
-> 程序设计者必须把现实生活中的实现方式转换成计算机中的实现方式。
-> 程序设计者不仅需要考虑程序要做什么,还要解决怎么做,要根据程序要做什么的要求,具体设计出计算机执行的每一个步骤,写出一个个语句,安排好他们的执行顺序。
-> 基于过程化设计是每个程序员的基本功,是大多数人学习程序的第一步。
-> 从学习基于过程的程序设计入手进而学习面向对象的程序设计是学习C++的较好途径之一。
一个基于过程的程序应该包含以下两个方面:
对数据的描述:数据结构
对操作的描述:操作步骤也即是算法
程序=算法+数据结构 -
算法
算法是处理问题的一系列步骤,算法必须具体的指出在执行时,每一步应当怎么做。 -
举例子
比如有五组人,每组两个队友进行比赛,取最高的分数,计算五组成绩的平均分和总分,请用程序描述? -
缺点
1.软件重用性差
2.软件可维护性差
3.开发出来的软件不能满足用户需要
2.面向对象的程序设计
-
序言
对于规模比较小的程序,开发者可以直接哟弄面向过程的方式开发程序,但是当程序规模变大的时候,就显得力不从心了。
C++面向对象的机制就为解决大程序中的困难产生的。 -
类
在面向对象程序设计中,除了主函数,其他函数都在类中,只有通过类才能调用类中的函数,程序的基本构成单位是类,程序面对的是一个类和对象。
显然这时候程序设计是基于类(基于对象的),而不是基于函数或者过程的。 -
基于对象程序设计
凡是以类对象为基本构成单位的程序称为基于对象的程序。为了与基于过程比较,往往把对象的程序设计和面向对象程序设计统称为面向对象程序设计。 -
面向对象程序设计
面向对象程序设计和人们日常生活中处理问题的思路是相似的。
在自然界和社会生活中,一个复杂的事物总是有很多部分组成的。
比如:一个人由手、脚、胳膊、身体、头、腰等组成。 -
对象
客观世界任何一个事物都可以看成一个对象,或者说客观世界是由千千万万个对象构成的。对象可以是一个汽车一个人一本书一个杯子甚至是一粒沙子。 -
对象的要素
1.属性
比如一个班级作为一个对象,那么属性就是学生人数、所在教室、专业等静态特征都属于属性。
== 2.行为==
比如学生学习、开会、体育比赛等会产生一种过程和结果的动态能力称为行为。 -
总结
任何一个对象都应当具备两个要素,即属性和行为。对象是由一组属性和一组行为构成的。
3.面向对象的特点
C++中每个对象都是由数据和函数构成的。其中,数据体现了前面提到的属性,函数体现了前面提到的行为。
-
封装的特点
可以对一个对象进行封装处理,把它的部分属性和功能对外屏蔽,也就是对外是看不到的,甚至是不可知的。
C++类中会对属性和函数行为进行封装,只会暴露出对外需要的函数接口。这样做还有利于安全,保护数据不被非法使用。 -
抽象的特点
例如我们常说的人就是一种抽象。人分为黄种人,白种人,黑种人等。
C++中,类是对象的抽象,对象是类的特例,即类的具体表现形式。 -
继承的特点
例如:新的车型继承了原有的车型的某些特性,这就是继承。
C++中通过继承的特性,我们可以从现有的类中把它的全部特性继承给子类,让子类在建立之初就拥有父类的一些特性。 -
多态的特点
比如同样的吃饭行为。比如人吃米饭、面条等,狗吃狗娘、骨头,小羊吃草。所以同样的功能在不同对象身上表现出不同的行为的过程称为多态。
C++多态是指由继承而产生的不同的派生类,其对同一消息会做出不同的响应。多态是面向对象程序设计一个重要的特性,能增加程序的灵活性。
4.类和对象的作用
-
序言
类是C++十分重要的概念,它是实现面向对象的程序设计的基础。
C++对C语言的改进最重要的就是增加了类这样的一种类型。所以C++开始被称为带类的C。类是所有面向对象的语言的共同特征,所有面向对象的语言提供了这种类型。 -
程序
基于过程:程序=算法+数据结构
面向对象:对象=算法+数据结构
程序=(对象+对象+对象+…+对象)+消息 -
消息
消息的作用就是对对象的控制。程序设计的关键是设计好每一个对象以及确定向这些对象发出的命令,使各对象完成相应的操作。
基于过程设计特点
面向对象的特点
5.面向对象的软件开发
-
面向对象编程
OOA
分析阶段,需要和用户一起对用户需求做出精确的分析和明确的描述,从宏观角度概括出系统应该做什么,而不是怎么做。
OOD
根据上阶段分出来的需求模型,对每一部分进行具体的设计,首先进行类的设计包含继承与派生等。然后以这些类为基础进行程序设计,提出设计思路和方法,包括对算法的设计。在设计阶段并不涉及到具体编程语言,而是用伪代码或者流程图来描述。
OOP
根据面向对象的设计结果,使用一种计算机语言把它写成程序,显然应当选择支持面向对象设计的语言比如C++。 -
面向对象测试
OOT
写好程序之后进行严格测试
- 面向对象维护
OOSM
软件需要进行后期维护和优化,面向对象的程序维护起来会比较方便。
6.类的一般形式
- 类和对象
C++中对象的类型称为类,类代表某一批对象的共性和特性。类是对象的抽象,对象是类的具体实例。正如结构体和结构体变量一样。
- 声明类类型
class Student{
char name[20];
int age;
short sex;
void play(){
cout<<"play....."<<endl;
}
};
- 添加保护
#include<iostream>
#include<cstring>
using namespace std;
int main(void){
class Student{
//成员数据
private:
char name[20];
int age;
short sex;
//成员函数
public:
void play(){
cout<<"play....."<<endl;
}
};
return 0;
}
采用权限机制隔离数据和操作安全
- 类的一般形式
class 类名{
private:
私有数据和成员函数
public:
共有数据和成员函数
};
7.类内部定义成员函数
-
成员函数
成员函数也是函数,只不过专属于这个类的函数。一般用来操作成员数据。成员函数一般设置为public
类中定义的成员函数大多是为了处理成员属性的,一般含有getXXX()、setXXX()方法。也是为了保护私有数据以及提供公开访问能力。 -
示例
//Person.h
#include<iostream>
#include<cstring>
using namespace std;
class Person{
public:
char name[20];
int age;
double height;
char* getName(){
return name;
}
void setName(char p_name[]){
strcpy(name,p_name);
}
int getAge(){
return age;
}
void setAge(int p_age){
age=p_age;
}
double getHeight(){
return height;
}
void setHeight(double p_height){
height=p_height;
}
};
//main.cpp
#include "Person.h"
using namespace std;
int main() {
Person person;
person.age=23;
cout<<person.age<<endl;
return 0;
}
结果:23
8.类外部定义成员函数
//Person.h
#include<iostream>
#include<cstring>
using namespace std;
class Person{
public:
//类成员数据
char name[20];
int age;
double height;
//类成员函数声明
char* getName();
void setName(char p_name[]);
int getAge();
void setAge(int p_age);
double getHeight();
void setHeight(double p_height);
};
//类外部定义成员函数
char* Person::getName(){
return name;
}
void Person::setName(char p_name[]){
strcpy(name,p_name);
}
int Person::getAge(){
return age;
}
void Person::setAge(int p_age){
age=p_age;
}
double Person::getHeight(){
return height;
}
void Person::setHeight(double p_height){
height=p_height;
}
//main.cpp
#include "Person.h"
using namespace std;
int main() {
Person person;
strcpy(person.name,"lily");
cout<<person.name<<endl;
return 0;
}
结果:lily
9.定义普通类变量并赋值
- 对象成员的赋值
我们一般不直接操作数据,会使用成员函数来操作数据,这样既可以保证数据安全性,又可以对数据进行处理加工。
char name[20]="jack";
person.setName(name);
person.setAge(23);
person.setHeight(177.56);
#include "Person.h"
using namespace std;
int main() {
Person person;
char name[20]="jack";
person.setName(name);
person.setAge(23);
person.setHeight(177.56);
cout<<"jack:name="<<person.getName()<<",age="<<person.getAge()<<",height="<<person.getHeight()<<endl;
return 0;
}
- 定义指针变量
#include "Person.h"
using namespace std;
int main() {
//定义指针变量
Person person;
Person* p_person=&person;
//对指针变量赋值
char marry[20]="marry";
p_person->setName(marry);
p_person->setAge(23);
p_person->setHeight(162.11);
//输出指针变量
cout<<"marry:name="<<p_person->getName()<<",age="<<p_person->getAge()<<",height="<<p_person->getHeight()<<endl;
return 0;
}
10.类成员函数声明和实现分开
//Human.h
#include<iostream>
#include<cstring>
using namespace std;
class Human{
private:
//类成员数据
char name[20];
int age;
double height;
public:
//类成员函数声明
char* getName();
void setName(char p_name[]);
int getAge();
void setAge(int p_age);
double getHeight();
void setHeight(double p_height);
};
//Human.cpp
#include "Human.h"
char* Human::getName(){
return name;
}
void Human::setName(char p_name[]){
strcpy(name,p_name);
}
int Human::getAge(){
return age;
}
void Human::setAge(int p_age){
age=p_age;
}
double Human::getHeight(){
return height;
}
void Human::setHeight(double p_height){
height=p_height;
}
//main.cpp
#include "Human.h"
using namespace std;
int main() {
//定义变量
Human human;
//对变量赋值
char Anna[20]="anna";
human.setName(Anna);
human.setAge(25);
human.setHeight(160.11);
//输出变量
cout<<"marry:name="<<human.getName()<<",age="<<human.getAge()<<",height="<<human.getHeight()<<endl;
return 0;
}
结果:
11.类访问修饰符
-
类成员
成员属性
成员函数
成员函数一般用来操作成员属性,提供外界访问成员数据的能力。 -
数据封装
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记public、private、protected来指定的。
关键字:public、private、protected称为访问修饰符。 -
访问修饰符
一个类可以有多个public、protected或private标记区域。
每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是private。 -
可见性
-
案例
//Animal.h
#include<iostream>
using namespace std;
class Animal{
private:
int a;
void showA(){
cout<<"A"<<endl;
}
protected:
int b;
void showB(){
cout<<"B"<<endl;
}
public:
int c;
void showC(){
cout<<"C"<<endl;
}
};
//main.cpp
#include "Animal.h"
int main() {
Animal animal;
animal.showC();
return 0;
}
结果:C
- 总结
一般情况下,我们成员变量都是private私有类型。成员函数都是public类型。当有继承的时候可能会用到protected类型。
12.面向对象程序思想总结
第十二章 类和对象深入
1.类的默认构造方法
-
概述
通常,实例化一个类的时候,也即是创建一个对象时候,需要做一些初始化的工作。例如对数据成员进行赋值。 -
构造函数
C++提供构造函数来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时候自动执行。 -
语法
比如 Human类。默认构造方法。该函数可以省略,系统会自动帮我们创建。Human();
//People.h
#include<iostream>
using namespace std;
class People{
private:
char name[20];
int age;
short sex;
public:
People(){
cout<<"默认构造函数..........."<<endl;
}
};
//main.cpp
#include "People.h"
int main() {
People people;
return 0;
}
结果:
- 练习
//People.h
#include<iostream>
#include<cstring>
using namespace std;
class People{
private:
char name[20];
int age;
short sex;
public:
//默认构造函数,函数名与类名相同
People();
//自定义成员函数
char* getName();
};
People::People(){
cout<<"默认构造函数..........."<<endl;
strcpy(name,"lucy");
age=10;
sex=1;
}
char* People::getName(){
return name;
}
//main.cpp
#include "People.h"
int main() {
People people;
cout<<"people:name="<<people.getName()<<endl;
return 0;
}
结果:
2.类的带参构造函数
//Human.h
#include<iostream>
#include<cstring>
using namespace std;
class Human{
private:
//类成员数据
char name[20];
int age;
short sex;
public:
//默认构造函数
Human();
//带参构造函数
Human(char p_name[],int p_age,short p_sex);
//类成员函数声明
char* getName();
void setName(char p_name[]);
int getAge();
void setAge(int p_age);
double getSex();
void setSex(double p_sex);
};
//Human.cpp
#include "Human.h"
//默认构造函数
Human::Human(){
cout<<"Human的默认构造函数执行..."<<endl;
strcpy(name,"jack");
age=10;
sex=1;
}
//带参构造函数
Human::Human(char p_name[],int p_age,short p_sex){
cout<<"Human的带参构造函数执行..."<<endl;
strcpy(name,p_name);
age=10;
sex=1;
}
char* Human::getName(){
return name;
}
void Human::setName(char p_name[]){
strcpy(name,p_name);
}
int Human::getAge(){
return age;
}
void Human::setAge(int p_age){
age=p_age;
}
double Human::getSex(){
return sex;
}
void Human::setSex(double p_sex){
sex=p_sex;
}
//main.cpp
#include "Human.h"
int main() {
Human human1;
cout<<"human1:name="<<human1.getName()<<",age="<<human1.getAge()<<",sex="<<human1.getSex()<<endl;
char kaka[20]="kangkang";
Human human2(kaka,18,0);
cout<<"human2:name="<<human2.getName()<<",age="<<human2.getAge()<<",sex="<<human2.getSex()<<endl;
return 0;
}
3.类的复制构造函数
- 复制构造函数
语法:
Human(Human &human); //复制构造方法
Human.h
#include<iostream>
#include<cstring>
using namespace std;
class Human{
private:
//类成员数据
char name[20];
int age;
short sex;
public:
//默认构造函数
Human();
//带参构造函数
Human(char p_name[],int p_age,short p_sex);
//复制构造函数
Human(Human& human);
//类成员函数声明
char* getName();
void setName(char p_name[]);
int getAge();
void setAge(int p_age);
double getSex();
void setSex(double p_sex);
};
Human.cpp
#include "Human.h"
//默认构造函数
Human::Human(){
cout<<"Human的默认构造函数执行..."<<endl;
strcpy(name,"jack");
age=10;
sex=1;
}
//带参构造函数
Human::Human(char p_name[],int p_age,short p_sex){
cout<<"Human的带参构造函数执行..."<<endl;
strcpy(name,p_name);
age=10;
sex=1;
}
//复制构造函数
Human::Human(Human& human){
cout<<"Human的复制构造函数执行..."<<endl;
strcpy(name,human.getName());
age=human.getAge();
sex=human.getSex();
}
char* Human::getName(){
return name;
}
void Human::setName(char p_name[]){
strcpy(name,p_name);
}
int Human::getAge(){
return age;
}
void Human::setAge(int p_age){
age=p_age;
}
double Human::getSex(){
return sex;
}
void Human::setSex(double p_sex){
sex=p_sex;
}
//main.cpp
#include "Human.h"
void showHuman(string message,Human& human);
int main() {
Human human1;
showHuman("human1",human1);
char kaka[20]="kangkang";
Human human2(kaka,18,0);
showHuman("human2",human2);
Human human3(human2);
showHuman("human3",human3);
return 0;
}
void showHuman(string message,Human& human){
cout<<"name="<<human.getName()<<",age="<<human.getAge()<<",sex="<<human.getSex()<<endl;
}
结果:
4.构造函数重载和默认参数
//Human.h
#include<iostream>
#include<cstring>
using namespace std;
class Human{
private:
//类成员数据
char name[20];
int age;
short sex;
public:
//默认构造函数
Human();
//带部分参构造函数
Human(char p_name[]);
//带参构造函数
Human(char p_name[],int p_age,short p_sex);
//复制构造函数
Human(Human& human);
//带默认参数的构造函数
Human(int p_age,short p_sex=0);
//类成员函数声明
char* getName();
void setName(char p_name[]);
int getAge();
void setAge(int p_age);
double getSex();
void setSex(double p_sex);
};
//Human.cpp
#include "Human.h"
//默认构造函数
Human::Human(){
cout<<"Human的默认构造函数执行..."<<endl;
strcpy(name,"jack");
age=10;
sex=1;
}
//带部分参构造函数
Human::Human(char p_name[]){
cout<<"Human的带部分参构造函数执行..."<<endl;
strcpy(name,p_name);
age=10;
sex=1;
}
//带参构造函数
Human::Human(char p_name[],int p_age,short p_sex){
cout<<"Human的带参构造函数执行..."<<endl;
strcpy(name,p_name);
age=p_age;
sex=p_sex;
}
//复制构造函数
Human::Human(Human& human){
cout<<"Human的复制构造函数执行..."<<endl;
strcpy(name,human.getName());
age=human.getAge();
sex=human.getSex();
}
//带默认参数的构造函数
Human::Human(int p_age,short p_sex){
cout<<"Human的带默认参数的构造函数执行..."<<endl;
strcpy(name,"Anna");
age=p_age;
sex=p_sex;
}
char* Human::getName(){
return name;
}
void Human::setName(char p_name[]){
strcpy(name,p_name);
}
int Human::getAge(){
return age;
}
void Human::setAge(int p_age){
age=p_age;
}
double Human::getSex(){
return sex;
}
void Human::setSex(double p_sex){
sex=p_sex;
}
//main.cpp
#include "Human.h"
void showHuman(string message,Human& human);
int main() {
Human human1;
showHuman("human1",human1);
char kaka[20]="Lily";
Human human4(kaka);
showHuman("human4",human4);
Human human2(kaka,23,0);
showHuman("human2",human2);
Human human3(human1);
showHuman("human3",human3);
Human human5(27);
showHuman("human5",human5);
return 0;
}
void showHuman(string message,Human& human){
cout<<message<<":name="<<human.getName()<<",age="<<human.getAge()<<",sex="<<human.getSex()<<endl;
}
结果:
5.类的析构函数
-
析构函数
析构函数也是一个特殊的成员函数,它的作用与构造函数相反,它的名字前面加一个~符号。析构函数功能主要是用来释放一个对象的,在删除对象前,做一些清理工作。 -
定义析构函数
~Human();//析构函数
//Man.h
#include<iostream>
#include<cstring>
using namespace std;
class Man{
private:
char* p_message;
public:
//默认构造函数
Man(){
cout<<"Man默认构造函数执行..."<<endl;
cout<<"给p_message成员数据开辟10个字符空间的内存!"<<endl;
p_message=new char[10];
strcpy(p_message,"hello");
}
//析构函数
~Man(){
cout<<"清理p_message的空间!"<<endl;
delete[] p_message;
}
char* getMessage(){
return p_message;
}
};
//Man.cpp
#include "Man.h"
int main(){
Man man;
cout<<man.getMessage()<<endl;
return 0;
}
结果:
注意:析构函数只有一个不能重载。无返回值
6.构造函数和析构函数的顺序
-
构造函数
C++提供构造函数来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是建立对象时候自动执行。 -
析构函数
析构函数也是一个特殊的成员函数,它的作用与构造函数相反,它的名字前面加一个~符号。析构函数功能主要是用来释放一个对象的,在删除对象前,做一些清理工作。
在使用构造函数和析构函数时候,特别需要注意他们调用时间和调用顺序。 -
顺序
一般情况下调用析构函数顺序和调用构造函数顺序相反。先构造后析构,后构造的先析构。 -
示意图
//Man.h
#include<iostream>
#include<cstring>
using namespace std;
class Man{
private:
int a;
public:
//默认构造函数
Man(int p_a){
a=p_a;
cout<<"Man默认构造函数执行:a="<<a<<endl;
}
//析构函数
~Man(){
cout<<"析构函数执行:a="<<a<<endl;
}
};
//Man.cpp
#include "Man.h"
int main(){
Man man1(1);
Man man2(2);
Man man3(3);
return 0;
}
结果:
7.this指针的使用
-
指针
变量的地址称为指针。 -
This指针
每个成员函数都包含一个特殊的指针,这个指针名字是固定的,称为this。它是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始指针。 -
使用this指针不改参数名
//Rabbit.h
#include<iostream>
using namespace std;
class Rabbit{
private:
short sex;
int age;
int code;
public:
//默认构造函数
Rabbit(){
sex=1;
age=6;
code=1001;
}
//带参构造函数
Rabbit(int age,short sex,int code){
this->age=age;
this->sex=sex;
this->code=code;
}
void showRabbit(){
cout<<"Rabbit:编号="<<this->code<<",年龄="<<this->age<<",性别="<<this->sex<<endl;
}
};
//Rabbit.cpp
#include "Rabbit.h"
int main(){
Rabbit rabbit(4,0,1005);
rabbit.showRabbit();
}
结果:
8.对象赋值和复制
- 对象赋值
如果一个类定义了两个或者多个对象,则这些对象直接可以相互赋值的。或者说一个对象的值可以赋值给另一个对象。这里说的对象的值,指的是对象中所有数据成员的值。 - 运算符=
对象之间的赋值也是可以通过=运算符进行赋值。其实=运算符本来只能进行单个变量之间的赋值的,现在被扩展为两个同类对象之间的赋值,这其实是通过对赋值运算符的重载实现的。
//Rabbit.cpp
#include "Rabbit.h"
int main(){
Rabbit rabbit1(4,0,1005);
rabbit1.showRabbit();
Rabbit rabbit2;
rabbit2.showRabbit();
//对象赋值
rabbit2=rabbit1;
rabbit2.showRabbit();
}
- 复制
对象赋值是通过复制构造函数实现的。类名 对象2(对象1) - 区别
1.对象赋值是对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋值。
2.对象复制则是从无到有建立一个新的对象,并使得它与自己已有的对象完全相同。
9.类的静态成员属性
-
静态数据成员
如果希望对象中是数据成员的值一样的,就可以把它定义为静态数据成员,这样它就为各个对象共有,而不是属于某一个对象。静态数据成员在内存空间只占用一份空间。关键字static -
静态成员属性
static int count //静态成员属性
//Stat.h
#include<iostream>
using namespace std;
class Stat{
public:
//静态成员属性
static int count;
};
//初始化
int Stat::count=2;
//Stat.cpp
#include "Stat.h"
int main(){
Stat stat1,stat2;
//对象调用
cout<<"对象Stat:count="<<stat1.count<<endl;
cout<<"对象Stat:count="<<stat2.count<<endl;
//类调用
cout<<"类调用静态属性Stat:count="<<Stat::count<<endl;
stat1.count++;
stat2.count++;
Stat::count++;
cout<<"类调用静态属性Stat:count="<<Stat::count<<endl;
return 0;
}
10.类的静态成员函数
静态成员函数是类的一部分不是对象的一部分。
如果要在类外面调用静态成员函数需要使用::
关键字static
//Brank.h
#include<iostream>
using namespace std;
class Brank{
private:
int code;
static int count;
public:
Brank();
static int getTotal();
};
//Brank.cpp
#include "Brank.h"
//静态成员赋值
int Brank::count=0;
Brank::Brank() {
count++;
}
int Brank::getTotal(){
return count;
}
//main.cpp
#include "Brank.h"
int main(){
Brank brank1,brank2,brank3;
cout<<"银行账户人数为:"<<Brank::getTotal()<<endl;
return 0;
}
静态成员函数不能引用普通数据成员。静态成员函数没有this指针。
11.嵌套类的使用
- 定义
C++允许在一个类中定义另一个类,称为嵌套类。
//Worker.h
#include<iostream>
using namespace std;
class Worker{
private:
int age;
class Place{
private:
int code;
public:
Place();
int getCode();
}place;
public:
Worker();
Worker(int age);
int getAge();
Place getPlace();
};
//Worker.cpp
#include "Worker.h"
Worker::Worker(){
this->age=18;
}
Worker::Worker(int age){
this->age=age;
}
int Worker::getAge(){
return this->age;
}
Worker::Place::Place(){
this->code=1009;
}
int Worker::Place::getCode(){
return this->code;
}
Worker::Place Worker::getPlace(){
return this->place;
}
//main.cpp
#include <iostream>
#include "Worker.h"
int main() {
Worker worker(25);
cout<<"Worker:age="<<worker.getAge()<<",code="<<worker.getPlace().getCode()<<endl;
return 0;
}
12.局部类的使用
- 定义
将类定义放在函数中,称为局部类。
#include <iostream>
using namespace std;
void showLocalClass();
int main() {
showLocalClass();
return 0;
}
void showLocalClass(){
//局部类的使用
class Student{
private:
int age;
short sex;
public:
Student(){
cout<<"Student局部类的构造函数...."<<endl;
this->age=0;
this->sex=0;
}
Student(int age,short sex){
this->age=age;
this->sex=sex;
}
void showStudent(){
string sexstr;
if(this->sex==1){
sexstr="男";
}
else{
sexstr="女";
}
cout<<"LocalClassStudent:age="<<this->age<<",sex="<<sexstr<<endl;
}
};
Student s1;
s1.showStudent();
Student s2(9,0);
s2.showStudent();
}
结果:
13.类的练习题一
1.定义一个汽车类,并在构造函数中初始化数据,在析构函数中清理数据。
//Car.h
#include<iostream>
using namespace std;
class Car{
private:
int* age;
int* code;
double* price;
public:
Car();
~Car();
void showCar();
};
//Car.cpp
#include "Car.h"
Car::Car(){
cout<<"Car默认构造函数"<<endl;
age=new int(10);
code=new int(1005);
price=new double(100.00);
}
Car::~Car(){
cout<<"Car默认析构函数"<<endl;
delete age;
delete code;
delete price;
cout<<"Car完成清理"<<endl;
}
void Car::showCar(){
cout<<"Car:age="<<(*age)<<",code="<<(*code)<<",price="<<(*price)<<endl;
}
//main.cpp
#include "Car.h"
int main() {
Car car;
car.showCar();
return 0;
}
14.类的练习题二
2.建立一个对象数组,内放5个同学的数据包含学号和成绩等,输出1、3、5学生数据。
//Student.h
#include<iostream>
using namespace std;
class Student{
private:
int code;
int score;
public:
Student(int code,int score);
int getCode();
int getScore();
};
//Student.cpp
#include "Student.h"
Student::Student(int code,int score){
this->code=code;
this->score=score;
}
int Student::getCode(){
return code;
}
int Student::getScore(){
return score;
}
//main.cpp
#include "Student.h"
int main() {
Student s1(1001,80),s2(1002,85),s3(1003,87),s4(1004.4,90),s5(1005,100);
Student ss[5]={s1,s2,s3,s4,s5};
for(int i=0;i<5;i++){
if((i+1)%2!=0){
cout<<"学生对象数组:code="<<ss[i].getCode()<<",score="<<ss[i].getScore()<<endl;
}
}
return 0;
}
15.类的练习题三
1.定义一个手机类,并利用this指针进行操作。
//Phone.h
#include<iostream>
using namespace std;
class Phone{
private:
int code;
int color;
public:
Phone(int code,int color){
this->code=code;
this->color=color;
}
void showPhone(){
cout<<"Phone:code="<<this->code<<",color="<<this->color<<endl;
}
};
//main.cpp
#include "Phone.h"
int main() {
Phone phone(9999,255);
phone.showPhone();
return 0;
}
2.定义一个颜色类,使用到嵌套类和局部类的知识点。
//Color.h
#include <iostream>
using namespace std;
class Color{
private:
int red;
int yellow;
int blue;
public:
Color(){
}
Color(int r,int y,int b){
this->blue=b;
this->red=r;
this->yellow=y;
}
int getRed(){
return red;
}
int getBlue(){
return blue;
}
int getYellow(){
return yellow;
}
};
//Phone.h
#include<iostream>
#include "Color.h"
using namespace std;
class Phone{
private:
int code;
Color color;
public:
Phone(int code,int red,int yellow,int blue){
this->code=code;
Color color(red,yellow,blue);
this->color=color;
}
void showPhone(){
cout<<"Phone:code="<<this->code<<",color->blue="<<this->color.getBlue()<<",color->red="<<color.getRed()<<",color->yello="<<color.getYellow()<<endl;
}
};
//main.cpp
#include "Phone.h"
int main() {
Phone phone(9999,255,101,172);
phone.showPhone();
return 0;
}
结果:
16.静态成员练习题
统计学生成绩(包含总成绩和平均成绩),使用静态成员?
//Teacher.h
#include<iostream>
using namespace std;
class Teacher{
private:
int num;
int age;
float score;
static int count;
static float sum;
public:
Teacher(int num,int age,float score);
void calc();
static float getSum();
static float getAvg();
};
//Teacher.cpp
#include "Teacher.h"
int Teacher::count=0;
float Teacher::sum=0;
Teacher::Teacher(int nume,int age,float score){
this->num=num;
this->age=age;
this->score=score;
count++;
}
void Teacher::calc(){
sum+=this->score;
}
float Teacher::getSum(){
return sum;
}
float Teacher::getAvg(){
if(count==0){
return 0;
}
return sum/count;
}
//main.cpp
#include "Teacher.h"
int main() {
Teacher stu[3]={
Teacher(1001,23,98),
Teacher(1002,24,89),
Teacher(1003,34,76)
};
for(int i=0;i<3;i++){
stu[i].calc();
}
//输出平均成绩
cout<<"平均总成绩为:"<<Teacher::getSum()<<endl;
cout<<"平均成绩为:"<<Teacher::getAvg()<<endl;
return 0;
}
第十三章 C++友元和命名空间
1.友元的介绍
一个类一般可以有私有和共有成员。在类外面可以访问公有成员,只有在类内才能访问私有成员。
-
友元
有元可以访问与其好友关系的类中的私有成员。 -
友元类型
友元函数、友元类 -
友元函数
如果在本类以外的其他地方定义了一个函数。在这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数。
在类体中用friend对其进行声明,此函数就称为本类的友元函数。友元函数就可以访问这个类的私有成员。 -
友元类
不仅仅可以将一个函数声明为一个类的朋友,也可以将一个类声明为本类的朋友,那么被设置为朋友的这个类就可以访问本类的所有成员,就是本类的友元类。
2.普通友元函数
- 友元
友元可以访问与其有好友关系的类中的私有成员。
注意:
友元函数的使用有三个要点:
1.在类定义中用关键字friend声明;
2.在类定义这外定义;
3.使用类对象引用作参数;
引用可以提高效率,否则创建副本,增加内存压力。
//Time.h
#include<iostream>
using namespace std;
class Time{
private:
int hour;
int minute;
int second;
public:
Time(int h,int m,int s);
friend void display(Time& time);
};
//Time.cpp
#include "Time.h"
Time::Time(int h,int m,int s){
this->hour=h;
this->minute=m;
this->second=s;
}
void display(Time& time){
cout<<"Time:hour="<<time.hour<<",minute="<<time.minute<<",second="<<time.second<<endl;
}
//main.cpp
#include "Time.h"
int main() {
Time t(22,30,1);
display(t);
return 0;
}
3.友元成员函数
//Water.h
#include<iostream>
#include "Drink.h"
using namespace std;
class Water{
private:
int a;
int b;
int c;
public:
Water(int a,int b,int c);
//Drink成员函数 是Water的友元函数
friend void Drink::showWater(Water& water);
};
//Water.cpp
#include "Water.h"
Water::Water(int a,int b,int c){
this->a=a;
this->b=b;
this->c=c;
}
void Drink::showWater(Water& water){
cout<<"Water:a="<<water.a<<",b="<<water.b<<",c="<<water.c<<endl;
}
//Drink.h
#include<iostream>
using namespace std;
class Water;
class Drink{
private:
int f;
int g;
int h;
public:
Drink(int f,int g,int h);
void showWater(Water& water);
};
//Drink.cpp
#include "Drink.h"
Drink::Drink(int f,int g,int h){
this->f=f;
this->g=g;
this->h=h;
}
//main.cpp
#include <iostream>
#include "Water.h"
int main() {
Water water(8,2,3);
Drink drink(4,5,6);
drink.showWater(water);
return 0;
}
4.友元类的讲解
-
友元类
不仅仅可以将一个函数声明为一个类的朋友,也可以将一个类声明为本类的朋友,那么被设置为朋友的这个类就可以访问本类的所有成员,就是本类的友元类。 -
语法
friend 类名
//Apple.h
#include<iostream>
#include "Banana.h"
using namespace std;
class Apple{
private:
int a;
int b;
int c;
public:
Apple(int a,int b,int c);
friend Banana;
};
//Apple.cpp
#include "Apple.h"
Apple::Apple(int a,int b,int c){
this->a=a;
this->b=b;
this->c=c;
}
void Banana::showApple(Apple& apple){
cout<<"Apple:a="<<apple.a<<",b="<<apple.b<<",c="<<apple.c<<endl;
}
//Banana.h
#include<iostream>
using namespace std;
class Apple;
class Banana{
private:
int d;
int e;
int f;
public:
Banana(int d,int e,int f);
void showApple(Apple& apple);
};
//Banana.cpp
#include "Banana.h"
Banana::Banana(int d,int e,int f){
this->d=d;
this->e=e;
this->f=f;
}
//main.cpp
#include "Apple.h"
int main() {
Apple apple(11,2,33);
Banana banana(44,55,66);
banana.showApple(apple);
return 0;
}
5.命名空间的使用
- 定义命名空间
命名空间一般形式如下:
namespace 名称{
常量、变量、函数、类等
} - 实例
namespace N1{
int a=1;
int b=2;
}
- 代码完整演示
//N1.h
#include<iostream>
using namespace std;
namespace N1{
int a=1;
int b=2;
void showWater(){
cout<<"I am water"<<endl;
}
}
//N2.h
#include<iostream>
using namespace std;
namespace N2{
int a=2;
int b=2;
void showWater(){
cout<<"drink water"<<endl;
}
}
//main.cpp
#include<iostream>
using namespace std;
#include "N1.h"
#include "N2.h"
using namespace N1;
using namespace N2;
int main() {
cout<<"N1:a="<<N1::a<<endl;
cout<<"N2:a="<<N2::a<<endl;
N1::showWater();
N2::showWater();
return 0;
}
结果:
6.嵌套命名空间
- 在多个文件中定义
定义命名空间时候:
通常会在头文件中声明命名空间中的函数,在源文件中进行定义。将程序声明和实现分开来。
//N1.h
#include<iostream>
using namespace std;
namespace N1{
int a=1;
int b=2;
void showWater(){
cout<<"N1:I am water"<<endl;
}
//嵌套命名空间
namespace N3{
void show(){
cout<<"N3:I am student."<<endl;
}
}
}
//main.cpp
#include<iostream>
#include "N1.h"
using namespace std;
using namespace N1;
int main() {
cout<<"N1:a="<<N1::a<<endl;
N1::showWater();
N1::N3::show();
return 0;
}
7.友元的练习题
1.有一个学生类,请设置普通友元函数访问它的成员。
//N4.h
#include<iostream>
using namespace std;
namespace N4{
class Student{
private:
int age;
public:
Student(int age){
this->age=age;
}
friend void showStudent(Student& student);
};
void showStudent(Student& student){
cout<<"N4:Student:age="<<student.age<<endl;
}
};
//main.cpp
#include<iostream>
#include "N4.h"
using namespace std;
using namespace N4;
int main() {
N4::Student student(19);
N4::showStudent(student);
return 0;
}
结果:
2.有一个学生类,设置老师类的成员函数来访问学生类。
//N5.h
#include<iostream>
using namespace std;
namespace N5{
class Student;
class Teacher{
private:
int age;
public:
Teacher(int age){
this->age=age;
}
void showStudent(Student& student);
};
class Student{
private:
int age;
public:
Student(int age){
this->age=age;
}
friend void Teacher::showStudent(Student& student);
};
void Teacher::showStudent(Student& student){
cout<<"N5:Teacher:showStudent age="<<student.age<<endl;
}
}
//main.cpp
#include<iostream>
#include "N5.h"
using namespace std;
using namespace N5;
int main() {
N5::Student student(19);
N5::Teacher teacher(32);
teacher.showStudent(student);
return 0;
}
第十四章 运算符重载
1.运算符重载介绍
-
重载
所谓重载就是赋予新的含义。函数重载是对一个已有的函数赋予新的含义,使之实现新的功能。 -
运算符的重载
前面我们说函数可以重载,其实运算符也可以重载,而我们其实已经在不知不觉中用到了运算符的重载,比如:
5+6、5.2+9、3.2+8.7等
计算机处理整数、单精度以及双精度数据的操作方法是不同的。所以C++默认帮我们重载了+,使得+能够用于int float double类型不同的运算。 -
移位运算符的重载
"<<"是C++位运算符的左移运算符,但是在输出操作中又成为流输出运算符。
“>>”是C++为运算符的右移运算符,但是在输入操作中又成为流输入运算符。
C++对<<、>>进行了重载,用户在不同场景使用的时候效果是不一样的。 -
自定义重载
运算符重载语法:
函数类型 operator 运算符名称(形参){
重载处理
}
2.运算符重载的方法
- 运算符重载
运算符重载是定义一个重载运算符的函数,使指定的运算符不仅能实现原有的功能,而且能实现在函数中指定新的功能。在使用被重载的运算符的时候,系统会自动调用该函数,以实现相应的功能。运算符重载的实质是函数的重载
//Complex.h
#include<iostream>
#include<cstring>
using namespace std;
class Complex{
private:
double real;
double imag;
public:
Complex();
Complex(double real,double imag);
Complex operator+(Complex& c);
void display(string msg);
};
//Complex.cpp
#include "Complex.h"
Complex::Complex(){
real=0;
imag=0;
}
Complex::Complex(double real,double imag){
this->real=real;
this->imag=imag;
}
Complex Complex::operator+(Complex& c){
Complex result;
result.real=this->real+c.real;
result.imag=this->imag+c.imag;
return result;
}
void Complex::display(string msg){
cout<<msg<<"("<<this->real<<","<<this->imag<<")"<<endl;
}
//main.cpp
#include "Complex.h"
int main() {
Complex c1(2,3),c2(4,-2),c3;
c3=c1+c2;
c1.display("c1:");
c2.display("c2:");
c3.display("c3:");
return 0;
}
3.重载运算符的规则
-
规则一
C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载。 -
规则二
C++允许重载的运算符
不可重载运算符有:
成员访问运算符 .
成员访问指针运算符 . * ,-> *
域运算符 ::
长度运算符 sizeof
条件运算符 ?:
预处理符号 # -
规则三
重载不能改变运算符运算对象的个数(操作数的个数)。例如><是双目运算符,重载之后还是需要两个参数。 -
规则四
重载不能改变运算符的优先级。例如* /由于+ -重载之后还是一样。 -
规则五
重载不能改变运算符的结合性。例如=是右结合(右至左)。 -
规则六
重载运算符的函数不能有默认的参数。否则就改变了运算符参数的个数,与规则三矛盾。 -
规则七
重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应用一个是类对象(或类对象的引用)
也就是说,参数不能全部是C++标准类型。防止用户修改标准数据类型的运算符的运算性质。如:错误的
//错误举例
int operator +(int a,int b){
return a-b;
}
- 规则八
用于类对象的运算符一般必须重载,但有两个例外=和&不必重载。系统已经帮我们重载了。
4.一元运算符重载
- 说明
一元运算符只有一个操作数
比如:int x=2 -x=-2
//Point.h
#include<iostream>
using namespace std;
class Point{
private:
int x;
int y;
public:
Point();
Point(int x,int y);
Point operator-();
void showPoint();
};
//Point.cpp
#include "Point.h"
Point::Point(){
x=0;
y=0;
}
Point::Point(int x,int y){
this->x=x;
this->y=y;
}
Point Point::operator-(){
Point p;
p.x=-x;
p.y=-y;
return p;
}
void Point::showPoint(){
cout<<"Point:x="<<x<<",y="<<y<<endl;
}
//main.cpp
#include "Point.h"
int main() {
Point p(7,9),p1;
p1=-p;
p.showPoint();
p1.showPoint();
return 0;
}
结果
5.二元运算符重载
//Apple.h
#include<iostream>
using namespace std;
class Apple{
private:
int a;
int b;
public:
Apple();
Apple(int a,int b);
Apple operator+(Apple& apple);
void showApple();
};
//Apple.cpp
#include "Apple.h"
Apple::Apple(){
a=0;
b=0;
}
Apple::Apple(int a,int b){
this->a=a;
this->b=b;
}
Apple Apple::operator+(Apple& apple){
Apple app;
app.a=this->a+apple.a;
app.b=this->b+apple.b;
return app;
}
void Apple::showApple(){
cout<<"Apple:a="<<this->a<<",b="<<this->b<<endl;
}
//main.cpp
#include "Apple.h"
int main() {
Apple apple1(1,2),apple2(3,4),apple3;
apple3=apple1+apple2;
apple1.showApple();
apple2.showApple();
apple3.showApple();
return 0;
}
结果:
6.关系运算符重载
-
关系运算符
C++支持各种关系运算符。比如 >、<、=、<=、>=。 -
重载<运算符
//Banana.h
#include<iostream>
using namespace std;
class Banana{
private:
int a;
int b;
public:
Banana();
Banana(int a,int b);
bool operator<(Banana& banana);
void showBanana();
};
//Banana.cpp
#include "Banana.h"
Banana::Banana(){
a=0;
b=0;
}
Banana::Banana(int a,int b){
this->a=a;
this->b=b;
}
bool Banana::operator<(Banana& banana){
if(a<banana.a){
return true;
}
if(a==banana.a&&b<banana.b){
return true;
}
return false;
}
void Banana::showBanana(){
cout<<"Banana:a="<<a<<",b="<<b<<endl;
}
//main.cpp
#include "Banana.h"
int main() {
Banana ban(4,5),ban1(5,6);
if(ban<ban1){
cout<<"b1<b2"<<endl;
}
else{
cout<<"b1>=b2"<<endl;
}
return 0;
}
7.输出运算符重载
-
重载输出运算符
如果想要输出运算符输出对象,那么需要对它进行重载。
语法:ostream &operator<<(ostream& output,自定义类 &) -
重载输出<<
//Lemon.h
#include <iostream>
using namespace std;
class Lemon{
private:
int a;
int b;
public:
Lemon();
Lemon(int a,int b);
void showLemon();
friend ostream &operator<<(ostream& output,Lemon& lemon);
};
//Lemon.cpp
#include "Lemon.h"
Lemon::Lemon(){
a=0;
b=0;
}
Lemon::Lemon(int a,int b){
this->a=a;
this->b=b;
}
void Lemon::showLemon(){
cout<<"Lemon:a="<<a<<",b="<<this->b<<endl;
}
ostream &operator<<(ostream& output,Lemon& lemon){
output<<"Lemon:a="<<lemon.a<<",b="<<lemon.b<<endl;
return output;
}
//main.cpp
#include "Lemon.h"
int main() {
Lemon lem(6,8);
cout<<lem<<endl;
return 0;
}
- 为何使用友元函数
因为输出运算符要访问对象的私有成员。友元就可以直接访问。
8.输入运算符重载
- 重载输入运算符
如果想要输入运算符输出对象,那么需要对它进行重载。
语法:istream &operator>>(istream &input,自定义类&)
//Lemon.h
#include <iostream>
using namespace std;
class Lemon{
private:
int a;
int b;
public:
Lemon();
Lemon(int a,int b);
void showLemon();
friend ostream &operator<<(ostream& output,Lemon& lemon);
friend istream &operator>>(istream& input,Lemon& lemon);
};
//Lemon.cpp
#include "Lemon.h"
Lemon::Lemon(){
a=0;
b=0;
}
Lemon::Lemon(int a,int b){
this->a=a;
this->b=b;
}
void Lemon::showLemon(){
cout<<"Lemon:a="<<a<<",b="<<this->b<<endl;
}
ostream &operator<<(ostream& output,Lemon& lemon){
output<<"Lemon:a="<<lemon.a<<",b="<<lemon.b<<endl;
return output;
}
istream& operator>>(istream& input,Lemon& lemon){
cout<<"请输入a:"<<endl;
input>>lemon.a;
cout<<"请输入b:"<<endl;
input>>lemon.b;
return input;
}
//main.cpp
#include "Lemon.h"
int main() {
Lemon lemon1;
lemon1.showLemon();
cin>>lemon1;
cout<<lemon1<<endl;
return 0;
}
9.自增自减运算符重载
- 重载++
++分为前置和后置。重载的时候为了区分约定:在自增或者自减的重载函数中,增加一个int型参数,就认为是后置自增或者自减运算符函数。
//Time.h
#include <iostream>
using namespace std;
class Time{
private:
int hour;
int minute;
public:
Time();
Time(int h,int m);
void displayTime();
Time operator++();
Time operator++(int);
};
//Time.cpp
#include "Time.h"
Time::Time(){
hour=0;
minute=0;
}
Time::Time(int h,int m){
hour=h;
minute=m;
}
void Time::displayTime(){
cout<<"Time:"<<hour<<":"<<minute<<endl;
}
//前置自增
Time Time::operator++(){
++minute;
if(minute>=60){
++hour;
minute-=60;
}
return Time(hour,minute);
}
//后置自增
Time Time::operator++(int){
Time T(hour,minute);
++minute;
if(minute>=60){
++hour;
minute-=60;
}
return T;
}
//main.cpp
#include "Time.h"
int main() {
Time t1(15,59),t2,t3;
t1.displayTime();
t2=++t1;
t2.displayTime();
t3=t2++;
t3.displayTime();
t2.displayTime();
return 0;
}
10.赋值运算符重载
- 重载赋值运算符
赋值运算符可以让C++标准数据类型可以互相进行赋值。通过重载我们可以实现对象之间赋值操作。
//Student.h
#include <iostream>
using namespace std;
class Student{
private:
string name;
int age;
public:
Student();
Student(string name,int age);
void displayStudent();
void operator=(Student &student);
};
//Student.cpp
#include "Student.h"
Student::Student(){
name="jack";
age=18;
}
Student::Student(string name,int age){
this->name=name;
this->age=age;
}
void Student::displayStudent(){
cout<<"Student:name="<<name<<",age="<<age<<endl;
}
void Student::operator=(Student &student){
this->name=student.name;
this->age=student.age;
}
//main.cpp
#include "Student.h"
int main() {
Student s1("tom",35),s2;
s2=s1;
s2.displayStudent();
return 0;
}
11.函数调用运算符重载
- 函数调用重载
函数调用运算符()可以被重载用于类的对象。
//Orange.h
#include<iostream>
using namespace std;
class Orange{
private:
int a;
int b;
public:
Orange();
Orange(int a,int b);
void showOrange();
Orange operator()(int a,int b,int c);
};
//Orange.cpp
#include "Orange.h"
Orange::Orange(){
a=0;
b=0;
}
Orange::Orange(int a,int b){
this->a=a;
this->b=b;
}
void Orange::showOrange(){
cout<<"Orange:a="<<this->a<<",b="<<this->b<<endl;
}
Orange Orange::operator()(int a,int b,int c){
Orange o;
o.a=this->a+a+b+c;
o.b=this->b+a-b-c;
return o;
}
//main.cpp
#include "Orange.h"
int main() {
Orange o1(1,2),o2;
o1.showOrange();
o2=o1(3,4,5);
o2.showOrange();
return 0;
}
12.下标运算符重载
- 下标重载
下标操作符[]通常用于访问数组元素。重载该运算符用于增强操作C++数组的功能。
语法:int& operator[](int i);
//Eggs.h
#include<iostream>
using namespace std;
class Eggs{
private:
int element[12];
public:
Eggs();
int& operator[](int i);
void display();
};
//Eggs.cpp
#include "Eggs.h"
Eggs::Eggs(){
for(int i=0;i<12;i++){
element[i]=i;
}
}
int& Eggs::operator[](int i){
if(i>11){
cout<<"下标越界,返回第一个值!"<<endl;
return element[0];
}
return element[i];
}
void Eggs::display(){
cout<<"element[]=";
for (int i=0;i<12;i++){
cout<<element[i]<<" ";
}
cout<<endl;
}
//main.cpp
#include "Eggs.h"
int main() {
Eggs egg;
egg.display();
cout<<egg[4]<<endl;
return 0;
}
13.转换构造函数
-
标准类型数据间的转换
隐式转换
显示转换 -
隐式转换
在C++中,某些不同类型数据可以自动进行转换。例如:int a=5; a=7.6+5;
-
显示转换
类型名 数据:
如:(int)88.23 -
转换构造函数
作用将一个其他类型的数据转换成一个类的对象。
用户可以根据需要定义转换构造函数,在函数体中告诉系统如何进行转换。
注意:转换构造函数只有一个参数。如果有多个参数的,它就不是转换构造函数。
14.类型转换函数
-
语法
operator 类型名(){ } -
注意
在函数名前面不能指定函数类型,函数没有参数。其返回值由函数名中指定的类型名决定的。转换类型函数只能作为成员函数,因为转换主体是本类的对象。
//Wood.h
#include<iostream>
using namespace std;
class Wood{
private:
double a;
double b;
public:
Wood();
Wood(double a,double b);
operator double();
void displayWood();
};
//Wood.cpp
#include "Wood.h"
Wood::Wood(){
a=0;
b=0;
}
Wood::Wood(double a,double b){
this->a=a;
this->b=b;
}
Wood::operator double(){
return a+b;
}
void Wood::displayWood(){
cout<<"Wood:a="<<a<<",b="<<b<<endl;
}
//main.cpp
#include "Wood.h"
int main() {
Wood wood(2.11,3.22);
wood.displayWood();
double w=wood;
double w1=wood+4.65;
cout<<"wood="<<w<<endl;
cout<<"wood="<<w1<<endl;
return 0;
}
15.运算符重载练习题
定义一个复数类Complex
要求:
使用命名空间、重载加减乘除、能让复数类型和double类型相加。
//App.h
#include<iostream>
#include<cstring>
using namespace std;
namespace App{
class Complex{
private:
int real;
int imag;
public:
Complex(){
real=0;
imag=0;
}
Complex(int r,int i){
real=r;
imag=i;
}
void showComplex(string s){
cout<<s<<"real="<<this->real<<",imag="<<this->imag<<endl;
}
Complex operator+(Complex& complex){
return Complex(this->real+complex.real,this->imag+complex.imag);
}
Complex operator-(Complex& complex){
return Complex(this->real-complex.real,this->imag-complex.imag);
}
Complex operator*(Complex& complex){
return Complex(this->real*complex.real,this->imag*complex.imag);
}
Complex operator/(Complex& complex){
return Complex(this->real/complex.real,this->imag/complex.imag);
}
operator int(){
return this->real+this->imag;
}
};
}
//main.cpp
#include "App.h"
int main() {
App::Complex c1(8,6),c2(2,3),c3;
c1.showComplex("c1:");
c2.showComplex("c2:");
c3.showComplex("c3:");
c3=c1+c2;
c3.showComplex("c1+c2:");
c3=c1-c2;
c3.showComplex("c1-c2:");
c3=c1*c2;
c3.showComplex("c1*c2:");
c3=c1/c2;
c3.showComplex("c1/c2:");
cout<<"c3+6="<<c3+6<<endl;
return 0;
}
第十五章 C++继承与派生
1.继承与派生的介绍
-
地位
继承是面向对象的最重要的特点,如果不掌握继承,那就等于不掌握类和对象的精华,就是没有掌握面向对象的真谛。 -
软件可重用性
面向对象技术强调软件的可重用性。C++提供的继承机制,解决了软件的可重用性。 -
定义
一个类从已有类那里获得其已有特性,这种现象称为类的继承。 -
派生
从已有类产生一个新的子类,称为类的派生。 -
基类和派生类
被继承的类称为基类。继承基类的类称为派生类。
-
继承种类
单继承和多继承。 -
总结
派生类是基类的具体化,基类是派生类的抽象。
2.派生类的声明
- 派生类声明的一般形式
class 派生类名:[继承方式] 基类名{
派生类新增加的成员
}
其中[]是表示可选的意思,可选值为:公有继承、私有继承、保护继承。默认为私有继承。
//N1.h
#include <iostream>
using namespace std;
namespace N1{
class A{
public:
int x;
int y;
void displayA(){
cout<<"N1-A:x="<<x<<",y="<<y<<endl;
}
};
class B:public A{
public:
int z;
void displayB(){
cout<<"N1-B:x="<<x<<",y="<<y<<",z="<<z<<endl;
}
};
}
//main.cpp
#include "N1.h"
int main() {
N1::B b1;
b1.x=1;
b1.y=2;
b1.z=3;
b1.displayA();
b1.displayB();
return 0;
}
3.派生类的构成
- 三大部分
实际上并不是 把基类成员和派生类新增加的成员放在一起就是派生类。
包含三大部分:1.从基类接受成员。2.调整从基类接受的成员。3.派生类中增加新成员。
//Person.h
#include <iostream>
using namespace std;
class Person{
protected:
string name;
int age;
short sex;
public:
void show(){
cout<<"Person..."<<endl;
}
};
class Student:public Person{
private:
//新增成员
double score;
public:
//覆盖基类的同名函数
void show(){
cout<<"Student..."<<endl;
}
};
//main.cpp
#include "Person.h"
int main() {
Student student;
student.show();
return 0;
}
4.公有继承
-
继承的三种方式
公有继承、私有继承、保护继承 -
公有继承
在定义一个类时候将基类的继承方式指定为public时候,称为公有继承。 -
特点
当一个类派生自公有基类时:基类的公有成员也是派生类的公有成员;基类的保护成员也是派生类的保护成员;基类的私有成员不能直接被派生类访问。 -
公有继承访问属性
//N3.h
#include<iostream>
using namespace std;
namespace N3{
class A{
private:
int x;
protected:
int y;
public:
int z;
int getX(){
return x;
}
void setX(int x){
this->x=x;
}
};
class B:public A{
public:
int getY(){
return y;
}
void setY(int y){
this->y=y;
}
void display(){
cout<<"N3-B:x="<<this->getX()<<",y="<<y<<",z="<<z<<endl;
}
};
}
//main.cpp
#include "N3.h"
int main() {
N3::B b3;
b3.setX(1);
b3.setY(2);
b3.z=4;
b3.display();
return 0;
}
5.私有继承
-
特点
当一个类派生自私有基类时:基类的公有成员变成派生类的私有成员;基类的保护成员也是派生类的私有成员;基类的私有成员不能直接被派生类访问。 -
私有继承访问属性
//N4.h
#include<iostream>
using namespace std;
namespace N4{
class A{
private:
int x;
protected:
int y;
public:
int z;
int getX(){
return x;
}
void setX(int x){
this->x=x;
}
};
class B:private A{
public:
int getBX(){
return getX();
}
void setBX(int x){
setX(x);
}
int getY(){
return y;
}
void setY(int y){
this->y=y;
}
int getZ(){
return z;
}
void setZ(int z){
this->z=z;
}
};
}
//main.cpp
#include "N4.h"
int main() {
N4::B b4;
b4.setBX(3);
b4.setY(4);
b4.setZ(5);
cout<<"N4-B:x="<<b4.getBX()<<",y="<<b4.getY()<<",z="<<b4.getZ()<<endl;
return 0;
}
6.保护继承
-
保护成员
由protected声明的成员称为保护成员,保护成员不能被类外访问。保护成员可以本类或者由派生类成员函数访问。 -
可见性
-
保护继承
定义一个类时候将基类的继承方式制定为protected,称为保护继承。 -
特点
当一个类派生自保护基类时:基类的公有成员变成派生类的保护成员;基类的保护成员也是派生类的保护成员;基类的私有成员不能直接被派生类访问。 -
保护继承访问属性
//N5.h
#include<iostream>
using namespace std;
namespace N5{
class A{
private:
int x;
protected:
int y;
public:
int z;
int getX(){
return x;
}
void setX(int x){
this->x=x;
}
};
class B:protected A{
public:
int getBX(){
return getX();
}
void setBX(int x){
setX(x);
}
int getY(){
return y;
}
void setY(int y){
this->y=y;
}
int getZ(){
return z;
}
void setZ(int z){
this->z=z;
}
};
}
//main.cpp
#include "N5.h"
int main() {
N5::B b5;
b5.setBX(1);
b5.setY(2);
b5.setZ(3);
cout<<"N5-B:x="<<b5.getBX()<<",y="<<b5.getY()<<",z="<<b5.getZ()<<endl;
return 0;
}
7.多级派生的访问属性
-
场景
经常会有这种情况:A为基类,B继承A,C继承B,D继承C…称为多级派生现象。那么访问属性如何呢? -
多级派生
如图:A为基类,B为A的直接派生类,C为A的间接派生类。
//N6.h
#include<iostream>
using namespace std;
namespace N6{
class A{
private:
int x;
protected:
int y;
public:
int z;
};
class B:public A{
private:
int u;
protected:
int v;//y
public:
int w;//z
};
class C:protected B{
protected:
//v,y,w,z
public:
void show(){
//cout<<"x="<<x<<",y="<<y<<endl;
cout<<"v="<<v<<",w="<<w<<",y="<<y<<",z="<<z<<endl;
}
};
}
分析:分析多级派生的时候需要遵循按级别归类原则:一级一级往下分析:
首先B类是公有继承A类,则A的私有成员不可访问,A的保护和公有成员变成派生类的保护和公有成员。然后C保护继承B类,则B类的私有成员不可访问,B的保护和公有成员变成派生类的保护成员,则C可以访问保护成员vy以及公有成员wz。
注意:一般使用公有继承。
8.派生类默认构造函数
- 构造函数
主要用来给对象做初始化操作。系统会自动调用构造函数。
//N7.h
#include<iostream>
using namespace std;
namespace N7{
class A {
protected:
int x;
int y;
int z;
public:
A(){
cout<<"N7-A:A的默认构造函数"<<endl;
x=0;
y=0;
z=0;
}
};
class B :public A{
private:
int w;
public:
B(){
cout<<"N7-B:B的默认构造函数"<<endl;
this->x=1;
this->y=2;
this->z=3;
this->w=4;
}
void show(){
cout<<"N7-B:x="<<x<<",y="<<y<<",z="<<z<<",w="<<w<<endl;
}
};
}
//main.cpp
#include "N7.h"
int main() {
N7::B b7;
b7.show();
return 0;
}
结果:
9.派生类带参构造函数
派生类带参构造函数涉及到对基类数据的初始化。
- 一般形式
派生类构造函数(总参数表):基类构造函数名(参数表){
派生类中新增数据成员初始化
}
//N8.h
#include<iostream>
using namespace std;
namespace N8{
class A{
protected:
int x;
int y;
int z;
public:
A(int a,int b,int c){
cout<<"N8-A:A带参构造函数"<<endl;
this->x=a;
this->y=b;
this->z=c;
}
};
class B:public A{
private:
int w;
public:
B(int a,int b,int c,int d):A(a,b,c){
cout<<"N8-B:B带参构造函数"<<endl;
this->w=d;
}
void show(){
cout<<"N8-B:x="<<x<<",y="<<y<<",z="<<z<<",w="<<w<<endl;
}
};
}
//main.cpp
#include "N8.h"
int main() {
N8::B b8(1,2,3,4);
b8.show();
return 0;
}
结果:
10.派生类带子对象的构造函数
- 子对象
我们使用的大多数类的成员数据大部分都是标准数据类型比如:int double char等。实际上类成员还有可能包含类对象。
比如:
class Student{
private:
int age;
Apple a;
那么在数据成员初始化的时候如何对子对象初始化呢?
- 一般形式
派生类构造函数名(总参数):基类构造函数名(参数表),子对象名(参数表){
派生类中新增数据成员的初始化
}
//N9.h
#include <iostream>
using namespace std;
namespace N9{
class M {
private:
int a;
int b;
public:
M (int a,int b){
this->a=a;
this->b=b;
}
void show(){
cout<<"N9-M:a="<<a<<",b="<<b<<endl;
}
};
class A {
protected:
int x;
int y;
int z;
public:
A(int x,int y,int z){
this->x=x;
this->y=y;
this->z=z;
}
};
class B:public A{
private:
int w;
M m ;
public:
B(int x,int y,int z,int w,int a,int b ):A(x,y,z),m(a,b){
this->w=w;
}
void show(){
cout<<"N9-B:x="<<x<<",y="<<y<<",z="<<z<<",w="<<w<<endl;
m.show();
}
};
}
//main.cpp
#include "N9.h"
int main() {
N9::B b9(9,2,3,4,5,6);
b9.show();
return 0;
}
结果:
11.多层派生的构造函数
- 场景
一个类不仅可以派生出一个派生类,派生类还可以继续派生,形成派生层次结构。那么多级派生如何使用构造函数初始化数据呢?
//N10.h
#include<iostream>
using namespace std;
namespace N10{
class A{
protected:
int x;
int y;
int z;
public:
A(int x,int y,int z){
this->x=x;
this->y=y;
this->z=z;
}
};
class B:public A{
protected:
int a;
int b;
public:
B(int x,int y,int z,int a,int b):A(x,y,z){
this->a=a;
this->b=b;
}
};
class C:public B{
private:
int c;
int d;
public:
C(int x,int y,int z,int a,int b,int c,int d):B(x,y,z,a,b){
this->c=c;
this->d=d;
}
void show(){
cout<<"N10-C:x="<<x<<",y="<<y<<",z="<<z<<",a="<<a<<",b="<<b<<",c="<<c<<",d="<<d<<endl;
}
};
}
main.cpp
#include "N10.h"
int main() {
N10::C c10(1,2,3,4,5,6,7);
c10.show();
return 0;
}
结果:
- 原则
每层只需要关注上一层的构造函数即可。
12.派生类的析构函数
-
析构函数
析构函数好构造函数相反,在对象撤销之前做些清理工作。当对象被删除时候,系统会自动调用析构函数。 -
派生类的析构函数
在派生时,派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。
在派生类中可以根据自己需要定义自己的析构函数,用来对派生类中所增加的成员进行清理。
基类的清理工作仍然由基类的析构函数负责。
在执行派生类的析构函数时候,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。
//n11.h
#include<iostream>
using namespace std;
namespace N11{
class A{
protected:
int* x;
public:
A(int x){
this->x=new int(x);
}
~A(){
cout<<"N11-A析构函数执行"<<endl;
delete x;
}
};
class B:public A{
protected:
int* y;
public:
B(int x,int y):A(x){
this->y=new int(y);
}
~B(){
cout<<"N11-B析构函数执行"<<endl;
delete y;
}
};
class C:public B{
protected:
int* z;
public:
C(int x,int y,int z):B(x,y){
this->z=new int(z);
}
~C(){
cout<<"N11-C析构函数执行"<<endl;
delete z;
}
};
};
//main.cpp
#include "n11.h"
int main() {
N11::C c11(1,2,3);
return 0;
}
13.派生类构造函数和析构函数执行顺序
- 析构函数执行顺序
后创建的先析构
//n12.h
#include<iostream>
using namespace std;
namespace N12{
class A{
protected:
int x;
public:
A(int x){
cout<<"N12-A构造函数执行"<<endl;
this->x=x;
}
~A(){
cout<<"N12-A析构函数执行"<<endl;
}
};
class B:public A{
protected:
int y;
public:
B(int x,int y):A(x){
cout<<"N12-B构造函数执行"<<endl;
this->y=y;
}
~B(){
cout<<"N12-B析构函数执行"<<endl;
}
};
class C :public B{
protected:
int z;
public:
C(int x,int y,int z):B(x,y){
cout<<"N12-C构造函数执行"<<endl;
this->z=z;
}
~C(){
cout<<"N12-C析构函数执行"<<endl;
}
void show(){
cout<<"N12-C:x="<<x<<",y="<<y<<",z="<<z<<endl;
}
};
}
//main.cpp
#include "n11.h"
#include "n12.h"
int main() {
N11::C c11(1,2,3);
N12::C c12(1,2,3);
c12.show();
return 0;
}
14.多继承默认构造函数
-
场景
前面讨论的是单继承,即一个类是从另一个类派生而来的。实际上常常有这样的情况:一个类需要继承多个基类,从多个基类处获得需要的属性数据。 -
多继承一般形式
//n13.h
class D:public A,private B,protected C{
类D中新增成员
}
#include<iostream>
using namespace std;
namespace N13{
class A{
protected:
int a;
int b;
public:
A(){
cout<<"N13-A默认构造函数执行"<<endl;
this->a=1;
this->b=2;
}
};
class B{
protected:
int c;
int d;
public:
B(){
cout<<"N13-B默认构造函数执行"<<endl;
this->c=3;
this->d=4;
}
};
class C:public A,public B{
protected:
int e;
int f;
public:
C(){
cout<<"N13-C默认构造函数执行"<<endl;
this->e=5;
this->f=6;
}
void show(){
cout<<"N13-C:a="<<a<<",b="<<b<<",c="<<c<<",d="<<d<<",e="<<e<<",f="<<f<<endl;
}
};
}
//main.cpp
#include "n13.h"
int main() {
N13::C c13;
c13.show();
return 0;
}
15.多继承带参构造函数
//n14.h
#include<iostream>
using namespace std;
namespace N14{
class A{
protected:
int a;
int b;
public:
A(int a,int b){
cout<<"N14-A的带参构造函数"<<endl;
this->a=10;
this->b=11;
}
};
class B{
protected:
int c;
int d;
public:
B(int c,int d){
cout<<"N14-B的带参构造函数"<<endl;
this->c=c;
this->d=d;
}
};
class C:public A,public B{
private:
int e;
int f;
public:
C(int a,int b,int c,int d,int e,int f):A(a,b),B(c,d){
cout<<"N14-C的带参构造函数"<<endl;
this->e=e;
this->f=f;
}
void show(){
cout<<"N14-C:a="<<a<<",b="<<b<<",c="<<c<<",d="<<d<<",e="<<e<<",f="<<f<<endl;
}
};
}
//mian.cpp
#include "n14.h"
int main() {
N14::C c14(1,2,3,4,5,6);
c14.show();
return 0;
}
结果:
16.多重继承的二义性问题
//n15.h
#include<iostream>
using namespace std;
namespace N15{
class A{
protected:
int a;
int b;
public:
A(int a,int b){
this->a=a;
this->b=b;
}
void show(){
cout<<"A show()..."<<endl;
}
};
class B{
protected:
int c;
int d;
public:
B(int c,int d){
this->c=c;
this->d=d;
}
void show(){
cout<<"B show()..."<<endl;
}
};
class C :public A,public B{
public:
C(int a,int b,int c,int d):A(a,b),B(c,d){
cout<<"N15-C构造函数执行"<<endl;
}
};
}
//main.cpp
#include "n15.h"
int main() {
N15::C c15(1,2,3,4);
//产生二义性
//c15.show();
//解决程序出现二义性调用
c15.A::show();
c15.B::show();
return 0;
}
- 二义性
派生类C调用show方法时候,因为派生类没有就从基类寻找,发现基类有两个同名的成员函数,不知道要调用哪个,从而产生二义性。
17.虚基类初探
C++提供虚基类的方法,使得在继承间接共同基类时候只保留一份成员。
- 声明虚基类一般形式
class 派生类名:virtual 继承方式 基类名
//n16.h
class A{
};
class B:virtual public A{
};
class C:virtual public A{
};
#include<iostream>
using namespace std;
namespace N16{
class A{
protected:
int a;
public:
void show(){
cout<<"N16-A show()..."<<endl;
}
};
class B:virtual public A{
protected:
int b;
};
class C :virtual public A{
protected:
int c;
};
class D:public B,public C{
private:
int d;
};
}
//main.cpp
#include "n16.h"
int main() {
N16::D d16;
d16.show();
return 0;
}
18.虚基类的初始化
//n17.h
#include<iostream>
using namespace std;
namespace N17{
class A{
protected:
int a;
int b;
public:
A(int a,int b){
cout<<"N17-A构造函数执行"<<endl;
this->a=a;
this->b=b;
}
void show(){
cout<<"N17-A show()..."<<endl;
}
};
class B:virtual public A{
public:
B(int a,int b):A(a,b){
cout<<"N17-B:构造函数执行"<<endl;
}
};
class C :virtual public A{
public:
C(int a,int b):A(a,b){
cout<<"N17-C:构造函数执行"<<endl;
}
};
class D:public B,public C{
public:
D(int a,int b):A(a,b),B(a,b),C(a,b){
cout<<"N17-D:构造函数执行"<<endl;
}
};
}
//main.cpp
#include "n17.h"
int main() {
N17::D d17(2,3);
d17.show();
return 0;
}
-
分析
为什么最后的派生类需要给虚基类进行初始化?防止出现B和C中的构造函数对虚基类给出不同初始化参数而产生矛盾。 -
规定
最后的派生类不仅需要对其直接基类进行初始化,还要对虚基类进行初始化。 -
总结
那么虚基类的构造函数会被调用多次吗?不会的。C++编译系统只会执行最后的派生类对虚基类的构造函数的调用,而忽略了基类其他派生类对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。
19.基类与派生类的转换
-
子类型
通过前面三种继承方式的介绍,我们知道只有公有继承才较好的保留了基类的特性。基类的公有成员或保护成员的访问权限在派生类中被全部按照原样保留了下来,因此公有派生类具有基类的全部功能。因此只有公有派生类才是真正的子类型。 -
赋值兼容
不同数据类型之间在一定条件下可以进行类型的转换,例如整型可以赋值给双精度类型。double d=2;
这种不同类型数据之间的自动转换和赋值称为赋值兼容。
基类与派生类之间也有赋值兼容关系,可进行类型转换
1.派生类对象可以向基类对象赋值
#include<iostream>
using namespace std;
namespace N18{
class A{
protected:
int sex;
};
class B:public A{
private:
int age;
};
}
#include "n18.h"
int main() {
N18::A a;
N18::B b;
//派生类赋值于基类
a=b;
return 0;
}
可用子类(即公有派生类)对象对其基类进行对象赋值。在赋值时候舍弃派生类自己的成员,实际上赋值只是对数据成员进行赋值。
2.派生类对象可以替代基类对象向基类对象的引用进行赋值或者初始化
A a;
B b;
A &r=a;
//这时候r是a的别名,r和a共享一段存储单元
r=b;
//此时r不是b的别名,而是b中基类部分的别名,r与b中基类部分共享同一段内存。
3.如果函数参数时基类对象或者基类对象的引用,可以传输子类对象作为实参。
void show(A a){
...
}
show(b)
以上为学习笔记
视频学习资料请点击网页链接>>