以下为个人学习笔记和习题整理
课程:计算机程序设计(C++)- 西安交通大学 @ 中国大学 MOOC
https://www.icourse163.org/course/XJTU-46006
# 课堂笔记
采用继承式的抽象方法描述万物,减少代码重复。
class person | |
{ | |
protected: | |
char *name; // 人名 | |
char sex; // 性别 | |
char pid[19]; // 身份证号码 | |
int weight; // 体重 | |
int high; // 身高 | |
public: | |
person(); // 无参构造函数 | |
person(char *n,char s,char *p,int w,int h); // 有参构造函数 | |
void change_data(char *n,char s,char *p,int w,int h);// 修改数据 | |
void walking(int k,int v); // 以 v 速度行走 k 步 | |
void hearing(char *sentence); // 将字符串小写变大写,大写变小写输出 | |
void speek(int n); // 说出整数 num 的英文句子 | |
void writing(); // 在屏幕上画出汉字 “曲” | |
void print(); // 输出人的属性值 | |
void out(int a);// 翻译小于 1000 的整数 | |
~person(); // 析构函数 | |
} | |
// 模拟行走:以 v 档速度水平行走 k 步 | |
void person::walking(int k,int v) | |
{ | |
cout<<"\n"<<name<<"水平直线行走"<<k<<"步"<<endl; | |
for(int i=0;i<k;i++) | |
{ | |
cout<<' '<<"o_o"; | |
Sleep(1000/v); | |
cout<<"\b\b\b"; | |
} | |
} | |
// 模拟收听:将句子字母大变小,小变大 | |
void person::hearing(char *sentence) | |
{ | |
cout<<endl<<sentence<<endl; | |
char *p=new char[strlen(sentence)+1]; | |
strcpy(p,sentence); | |
char *pp=p; | |
while(*p) | |
{ | |
if(*p>='a' && *p<='z') | |
{ | |
*p='A'+(*p -'a'+0); | |
} | |
else if(*p>='A' && *p<='Z') | |
{ | |
*p='a'+(*p-'A'); | |
} | |
p++; | |
} | |
cout<<pp<<endl; | |
delete pp; | |
} | |
// 模拟说话:说出整数的英文句子 | |
void person::speek(int n) | |
{ | |
if(n>1999999999) | |
{ | |
cout<<"dev C++平台无法处理大于1999999999位的数!"<<endl; | |
} | |
else | |
{ | |
// 三位三位取出,存入 abcd 中 | |
int a=n/1000000000,b=(n%1000000000)/1000000 | |
int c=(n%1000000)/1000,d=n%1000; | |
if(a!=0) | |
{ | |
out(a); | |
cout<<"billion "; | |
} | |
if(b!=0) | |
{ | |
out(b); | |
cout<<"million "; | |
} | |
if(c!=0) | |
{ | |
out(c); | |
cout<<"thousand "; | |
} | |
if(d!=0) | |
{ | |
// 据英文语法规则,最后两位前一定有 and | |
if(d<100&&(a!=0||b!=0||c!=0)) | |
{ | |
cout<<"and "; | |
} | |
out(d); | |
} | |
cout<<endl; | |
} | |
} | |
int main() | |
{ | |
// 创建对象 | |
person Jack("James Chen",'M',"610103198901062493",160,180); | |
Jack.print(); // 输出人的属性值 | |
Jack.walking(20,4); // 行走 20 步,1/4 秒走一步 | |
Jack.hearing("Hi! You are simple!"); // 听英文句子 | |
Jack.speek(1006); // 说出整数 1006 的英文句子 | |
cout<<endl; | |
//Jack.writing (); // 书写汉字 “曲” | |
return 0; | |
} |
# 继承
# 概念
从一个或多个以前定义的类 (基类) 产生新类的过程称为派生,这个新产生的类又称为派生类。
类的继承(inheritance)是指新类从基类那里得到基类的特征,也就是继承基类的数据和函数。
派生的新类同时也可以增加或重新定义数据和函数,这就产生了类的层次性
派生和继承的概念也来自于人们认识客观世界的过程
# 好处
软件复用是软件设计中常用的手段
在程序设计中反复使用高质量的软件来缩短开发时间,提高效率(数量和质量)
客观世界中许多实体之间是有继承特性的
点→圆→圆柱体
人→学生→大学生
水果→桃→水蜜桃→陕西水蜜桃
# 派生类
采用已存在的类去定义建立新类
新类称为派生类(子类)
已存在的类称为基类(父类)
派生类与基类具有相对性
# 定义方法
class <派生类名>: <访问权限> <基类名1>,......<访问权限> <基类名n> | |
{ | |
private: | |
<新增私有数据成员和成员函数> | |
protected: | |
<新增保护数据成员和成员函效> | |
public: | |
<新增公有数据成员和成员函效> | |
}; |
派生出新类时,可以做如下几种变化:
- 可以增加新的数据成员
- 可以增加新的函数成员
- 可以重新定义已有的函数成员
- 可以改变现有成员的数据值
派生类的作用:
- 从基类接受成员
- 派生类对基类的扩充
- 派生类对基类成员的改造
- 系统的默认值就是私有继承
class sing_star:public person | |
{ | |
protected: | |
float salary; // 薪水 | |
public: | |
sing_star(); // 无参构造函数 | |
sing_star(char *n,char s,char *p,int w,int h,float s1); // 有参构造函数 | |
void change_data(char *n,char s,char *p,int w,int h,float s1); // 修改数据 | |
void playing(char *ps); // 演唱歌曲 | |
void print(); // 输出歌星属性值 | |
}; | |
// 模拟唱歌:播放 mp3 歌曲 | |
void sing_star::playing(char *ps) | |
{ | |
char str[100]="play "; //play 后有空格 | |
strcat(str,ps); | |
cout<<str; | |
mciSendString(str,NULL,0,NULL); | |
// 在 Dec-C++ 环境中还要进行设置:工具 \ 编译器选项 \ 编译器 \ 在连接器命令, 加入以下命令 \-lwinmm | |
//mciSendStringA (str,NULL,0,NULL); //Windows API VC2008 调 用此函数 | |
//mciSendString (str,NULL,0,NULL); //Windows API VC6.0 调用此函数 | |
char a; | |
cin>>a; // 输入任何字符结束播放 | |
} |
# 内嵌对象
class Date | |
{ | |
protected: | |
int year | |
int month; | |
int day; | |
public: | |
Date() | |
{ | |
year = 1900; | |
month = day = 1; | |
} | |
Date(int yy,int mm,int dd) | |
{ | |
init(yy,mm,dd); | |
}; | |
void init(int,int,int ); | |
void print_ymd(); | |
void print_mdy(); | |
}; |
class Time | |
{ | |
protected: | |
int hour; | |
int miniter; | |
int second; | |
public: | |
Time() | |
{ | |
hour = miniter = second = 0; | |
} | |
Time(int h,int m,int s) | |
{ | |
init(h,m,s); | |
}; | |
void init(int,int,int); | |
void print_time(); | |
}; |
class person:public Date,public Time | |
{ | |
// 注意包含了基类的出身日期和出身时间 | |
char name[20]; | |
char sex; | |
char pid[19]; | |
int weight; | |
int hight; | |
public: | |
person(); | |
person(char *n,char s,char *p,int w,int h,int hr,int mr,int sd); | |
void change_data(char *n,char s,char *p,int w,int h,int hr,int mr,int sd); | |
void walking(int k); | |
void hearing(char *sentence); | |
void speek(); | |
void writing(); | |
void ShowMe(); | |
}; | |
// 构造函数定义 | |
person::person() | |
{ | |
name=new char[strlen("XXXXXX")+1]; | |
strcpy(name,"XXXXXX"); | |
strcpy(pid,"XXXXXXXXXXXXXXXXXX"); | |
sex='X'; | |
weight=0; | |
high=0; | |
year=1900; | |
month=day=1; | |
hour=miniter=second=0; | |
} | |
person::person(char *n,char s,char *p,int w,int hh,int hr,int mr,int sd) | |
{ | |
change_data(n,s,p,w,hh,hr,mr,sd); | |
} | |
// 修改数据函数定义 | |
void person::change_data(char *n,char s,char *p,int w,int hh,int hr,int mr,int sd) | |
{ | |
name=new char[strlen(n)]; | |
strcpy(name,n); | |
strcpy(pid,p); | |
sex=s; | |
weight=w; | |
high=hh; | |
char temp[5]; // 通过身份证号码产生出身日期 | |
strncpy(temp,p+6,4); | |
year=atoi(temp); | |
strncpy(temp,p+10,2); | |
temp[2]='\0'; | |
month=atoi(temp); | |
strncpy(temp,p+12,2); | |
temp[2]='\0'; | |
day=atoi(temp); | |
hour=hr; | |
miniter=mr; | |
second=sd; | |
} | |
// 主函数定义 | |
int main() | |
{ | |
// 创建对象 | |
person Jack("James Chen",'M',"610103198901062493",160,180,23,34,35); | |
Jack.print(); // 输出人的属性值 | |
system("pause"); | |
Jack.walking(10,4); // 行走 10 步,1/4 秒走一步 | |
Jack.hearing("You are simple"); // 听英文句子 | |
Jack.speek(1006); // 说出整数 num 的英文句子 | |
cout<<endl; | |
//Jack.writing (); // 书写汉字 “曲” | |
return 0; | |
} |
# 三种继承方式
# public
公有继承方式
派生类对基类各种成员访问权限如下:
- 基类公有成员相当于派生类的公有成员,即派生类可以像访问自身公有成员一样访问从基类继承的公有成员
- 基类保护成员相当于派生类的保护成员,即派生类可以像访问自身的保护成员一样,访问基类的保护成员
- 派生类内部成员无法直接访问基类的私有成员
#include <iostream> | |
#include <cstring> | |
using namespace std; | |
// 人员类定义 | |
class Person | |
{ | |
protected: | |
char Name[10]; // 姓名 | |
int Age; // 年龄 | |
char Sex; // 性别 | |
public: | |
void Register(char *name, int age, char sex) // 设置数据成员 | |
{ | |
strcpy(Name, name); | |
Age = age; | |
Sex=(sex=='m'? 'm':'f'); | |
} | |
void ShowMe() // 输出数据成员 | |
{ | |
cout<<Name<<"\t"<<Sex<<"\t"<<Age<<"\t"; | |
} | |
}; | |
// 雇员类定义 | |
class Employee: public Person | |
{ | |
char Dept[20]; // 工作部门 | |
float Salary; // 月薪 | |
public: | |
Employee() | |
{ | |
EmployeeRegister("XXX",0,'m',"XXX",0); | |
} | |
void EmployeeRegister(char *name, int age, char sex, char *dept, float salary); | |
void ShowEmp(); // 显示雇员信息 | |
}; | |
void Employee::EmployeeRegister(char *name, int age, char sex, char *dept, float salary) | |
{ | |
Register(name,age,sex); | |
// 如果改成直接操作基类数据成员? | |
strcpy(Dept, dept); | |
Salary = salary; | |
} | |
void Employee::ShowEmp() | |
{ | |
cout<<Name<<"\t"<<Sex<<"\t"<<Age<<"\t"; | |
// 如果将基类 protected 改为 private? | |
cout<<Dept<<"\t"<<Salary; | |
} | |
int main()// 主函数 | |
{ | |
Employee emp; | |
emp.EmployeeRegister("张弓长",40,'f',"图书馆",2000); | |
emp.ShowEmp(); | |
// 张弓长 f 40 图书馆 2000 | |
cout<<endl; | |
emp.ShowMe(); | |
// 张弓长 f 40 | |
cout<<endl; | |
return 0; | |
} |
# private
私有继承方式
派生类对基类各种成员访问权限如下:
- 基类公有成员和保护成员都相当于派生类的私有成员,派生类只能通过自身的函数成员访问他们
- 对于基类的私有成员,无论派生类内部成员或派生类使用者都无法直接访问。
... | |
// 雇员类定义 | |
class Employee: private Person | |
... | |
int main()// 主函数 | |
{ | |
Employee emp; | |
emp.EmployeeRegister("张弓长",40,'f',"图书馆",2000); | |
emp.ShowEmp(); | |
// emp.ShowMe(); | |
// 本句违反继承规则 | |
// Showme () 是派生类的私有成员,只能成员函数访问,对象不能访问 | |
// 报错: 'Person' is not an accessible base of 'Employee' | |
return 0; | |
} |
# protected
保护继承方式
派生类对基类各种成员访问权限如下 :
- 基类的公有成员和保护成员都相当于派生类的保护成员,派生类可以通过自身的成员函数或其子类的成员函数访问他们
- 对于基类的私有成员,无论派生类内部成员或派生类使用者都无法直接访问
class Student : protected Person | |
{ | |
protected: | |
int Number; | |
char ClassName[10]; | |
public: | |
void Register(char *classname, int number, char *name, int age, char sex) | |
{ | |
strcpy(ClassName, classname); | |
Number = number; | |
strcpy(Name, name); // 正确,引用基类的保护成员 | |
Age = age; // 正确,引用基类的保护成员 | |
Sex = (sex == 'm'?'m':'f'); // 正确,引用基类的保护成员 | |
} | |
void ShowStu() | |
{ | |
cout << Number << '\t' << ClassName << '\t'; | |
ShowMe(); | |
} | |
}; | |
int main() | |
{ | |
Student stu; | |
stu.Register("计算机51",85071011,"张弓长",18,'m'); | |
stu.ShowStu(); | |
// stu.ShowMe(); | |
// 错误,对象不能访问保护成员 | |
return 0; | |
} |
# 小结
派生方式 | 基类中的访问限定 | 在派生类中对基类成员的访问限定 | 外部函数 如 main() |
---|---|---|---|
公有继承 | public | public | 可以直接访问 |
protected | protected | 不可以直接访问 | |
private | 不可以直接访问 | 不可以直接访问 | |
私有继承 | public | private | 不可以直接访问 |
protected | private | 不可以直接访问 | |
private | 不可以直接访问 | 不可以直接访问 | |
保护继承 | public | protected | 不可以直接访问 |
protected | protected | 不可以直接访问 | |
private | 不可以直接访问 | 不可以直接访问 |
# 派生类构造和析构函数
基类的构造函数与析构函数不能被继承
派生类构造函数的一般形式为:
<派生类名>::<派生类名>(<参数总表>): <基类名1>(<参数表1>),...,<基类名n>(<参数表n>),<内嵌对象名1>(<对象参数表1>),...,<内嵌对象名m>(<对象参数表m>) | |
{ | |
<派生类新增加成员的初始化>; | |
} |
# 执行顺序
#include<iostream> | |
#include<string.h> | |
using namespace std; | |
class Person | |
{ | |
char Name[10]; // 姓名 | |
int Age; // 年龄 | |
public: | |
Person(char* name,int age) | |
{ | |
strcpy(Name, name); | |
Age = age; | |
cout<<"constructor of person"<<Name<<endl; | |
} | |
~Person() { | |
cout<<"deconstrutor of person"<<Name<<endl; | |
} | |
}; | |
class Employee: public Person | |
{ | |
char Dept[20]; | |
Person Leader; // 内嵌对象 | |
public: | |
Employee(char *name, int age, char *dept, char *name1, int age1):Person(name,age), Leader(name1,age1) | |
{ | |
strcpy(Dept, dept); | |
cout<<"constructor of Employee"<<endl; | |
} | |
~Employee() { | |
cout<<"deconstrucor of Employee"<<endl; | |
} | |
}; | |
int main() | |
{ | |
Employee emp("张弓长",40,"人事处","李木子",36); | |
return 0; | |
} | |
//constructor of person 张弓长 | |
//constructor of person 李木子 | |
// constructor of Employee | |
// deconstrucor of Employee | |
//deconstrutor of person 李木子 | |
//deconstrutor of person 张弓长 |
构造函数的执行顺序
首先,调用基类构造函数,调用顺序按照它们被继承时声明的基类名顺序执行。
其次,调用内嵌对象构造函数,调用次序按各个对象在派生类内声明的顺序。
最后,执行派生类构造函数体中的内容。析构函数的执行顺序
派生类析构函数执行过程恰与构造函数执行过程相反。
首先,执行派生类析构函数。
然后,执行内嵌对象的析构函数。
最后,执行基类析构函数。
# 课堂讨论
- 对于 person 来说,Date 类和 Time 类作为基类合适,还是其对象作为 person 内嵌对象(即作为 person 的数据成员)合适?请说说理由。
其对象作为 person 内嵌对象(即作为 person 的数据成员)合适。
理由:
一个合理的派生,应该是:一个派生类对象 一定是 一个基类对象,而 Date 类和 Time 类和 person 类无直接关系,所以作为 person 内嵌对象比较合适。
- 构造函数可以继承吗?如果能,怎样初始化对象;如果不能,说说理由。
不能,构造函数是用来初始化类的对象,与父类的其他成员不同,他不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但是不继承父类的构造方法)。在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。
- 基类中的保护成员和私有成员被派生类公有继承后,在访问属性上有什么区别?(类内的访问限制和类外的访问限制)
基类中的保护成员被派生类公有继承后,在派生类内相当于保护成员,可以在派生类内直接访问、在类外不能直接访问;
基类中的私有成员被派生类公有继承后,在派生类内不能直接访问,在类外也不能被直接访问。
# 随堂练习
视频中
person
类的析构函数体里的delete
语句可以省略。下列叙述正确的是 。
在派生类的类体中,只能定义新增的数据成员和新增的函数成员。
可以在类外用
a.x
的形式访问派生类对象a
的基类成员x
,其中x
是 。在派生类的定义中,无论采用三种继承方式任何一种,都无法直接访问基类中的私有成员。