C++课程总结.docx
《C++课程总结.docx》由会员分享,可在线阅读,更多相关《C++课程总结.docx(40页珍藏版)》请在冰豆网上搜索。
![C++课程总结.docx](https://file1.bdocx.com/fileroot1/2023-2/9/ac6052d9-12ab-4429-aa9d-f414e380860a/ac6052d9-12ab-4429-aa9d-f414e380860a1.gif)
C++课程总结
2012年度课程总结
§1.教学要求
§2.面向对象程序设计语言基础
§3.类和对象
§4.继承和派生
§5.多态与重载
§6.构造函数、析构函数与成员初始化
§7.DAG、内存存储内容和访问控制表
§8.运算符重载与静态联编
§9.虚函数与动态联编
§10.静态成员与堆的动态管理
§1.教学要求
A.掌握面向对象程序设计的基本概念和原理:
——封装性
——继承性
——多态性
B.具备阅读和理解程序的能力,初步掌握编程技术
熟悉面向对象程序设计基本语言,同时熟悉派生类的访问控制表、基类及其派生类构造函数的调用顺序、基类及其派生类对象的内存存储内容、虚函数的功能、静态成员及堆空间的使用。
§2.面向对象程序设计语言基础
1.标识符与标点符号
A.标识符是由若干个字符组成的字符序列,用来命名程序中的一些实体。
关键字是系统预定义的具有特定含义的标识符。
语法规则:
①由字母(a~z,A~Z)、数字(0~9)或下划线(-)组成
②第一个字符必须是字母、下划线或@
③标识符中可包含关键字(如intx、myclass等),关键字不能作为新的标识符出现(如class、C#中的base等)
B.标点符号本身不表示一个产生值的操作。
常用的标点符号:
逗号,用作数据之间的分隔符
;分号,语句结束符
:
冒号,语句标号结束符或条件运算符
′单引号,字符常量标记符
″双引号,字符串常量标记符
{左花括号,复合语句开始标记符
}右花括号,复合语句结束标记符
C.使用“;”的场合:
①结构、联合和类的定义:
structstr{…};
unionuni{…};
classbase{…};
classderive:
publicbase{…};
②每条程序语句结尾:
inti;
cout<
fun(i);
③函数原型说明:
voidfun(int,char*);
double&fun(double&,char*);
D.不使用“;”的场合:
①预处理语句:
#include
#endif
②函数体(函数定义):
voidfun(inti,char*ptr){…}
double&fun(double&d,char*ptr){…returnd;}
③程序控制语句:
if(p){cout<
for(inti=0;i<10;i++){cout<
2.基本数据类型
A.整型。
用于定义整数对象
B.字符型。
用于定义字符数据
C.浮点型。
用于定义实数
D.空型。
用于声明没有返回值的函数、声明未确定类型或指向任意数据类型的指针。
变量不能声明为空类型。
3.常量、变量与数组
A.整型常量即整数,有短整型、长整型、无符号整型、符号整型等类型;实型常量即实数,有单精度与双精度之分;字符常量以单引号作为起止标记,中间为一个或若干个字符;字符串常量由一对双引号括起来的零个或多个字符序列组成;逻辑常量是逻辑类型(bool)值的表示(0或1);
B.变量属于某种数据类型,只有存在该数据类型后才可以定义对应的变量。
即:
变量须先定义后使用。
C.数组是由一组顺序排列的具有相同类型的变量组成的集合,数组中的每个变量成为数组元素。
数组与简单变量一样,须先定义后使用。
4.运算符、表达式和基本语句
A.运算符具有优先级。
++、--运算符两种使用格式的区别:
运算符在前,先执行增1或减1操作;运算符在后,后执行增1或减1操
B.按运算符不同,表达式可分为算术表达式、赋值表达式、关系表达式、逻辑表达式和逗号表达式等几种类型作
C.基本语句:
①表达式语句、空语句和复合语句
②选择语句
③循环语句
5.函数和引用
A.函数调用时,其调用格式应与其函数原型相一致
B.引用初始化时必须使用某个类型的变量
C.函数传值调用及引用调用:
数据传值调用是将实参的数据值传递给形参,地址传值调用是将实参的地址值传递给形参(C++),引用调用是将实参变量值传递给形参,形参是实参变量的别名。
§3.类和对象
1.类的定义
类的构成一般分为说明部分与实现部分,说明部分与实现部分可以都在类体内,也可以将接口与实现部分分离(C#中接口需用抽象类纯虚函数表示)
2.类成员及其访问属性
类的成员包括数据成员和成员函数,分为公有成员、私有成员和保护成员等。
公有成员可被自由访问,私有成员只允许类成员函数和友元函数(C++)访问,保护成员允许类成员函数、派生类成员函数和友元函数(C++)访问。
3.对象与对象成员的访问
对象是类的实例,在定义对象之前必须先定义类。
一个类被定义后并不占内存空间,只有类的实例化对象才占有内存空间。
对象成员的访问主要通过对象访问成员使用“.”运算符来实现(C++还可通过“->”来实现)。
§4.继承和派生
1.派生类及其对象
A.派生类除了能从基类继承所有成员以外,还具有以下功能:
1增加新的数据成员
2增加新的成员函数
3重新定义成员函数
4改变现有成员的属性
B.建立派生类后,派生类对象的内存存储内容既包括基类的也包括派生类的所有非静态数据成员,这些数据成员中不管是否能够访问,都一律存放在派生类对象的内存存储区内。
非静态数据的排列顺序是基类在前,派生类在后。
如果有虚函数,则还包括虚指针VPTR,该虚指针指向vtable。
2.派生类对基类成员的访问规则
A.基类中的私有成员在派生类中是隐藏的,只能在基类内部访问,这与继承方式无关
B.派生类中的成员不能访问基类中的私有成员,可以访问基类中的公有成员和保护成员,此时访问能力与继承方式无关
C.派生类从基类公有继承时,基类的公有成员和保护成员在派生类中属性不变,基类的私有成员不可访问
D.派生类从基类私有继承时,基类的公有成员和保护成员在派生类中都改变为私有成员,基类的私有成员不可访问
E.派生类从基类保护继承时,基类的的公有成员在派生类中改变为保护成员,基类的保护成员属性不变,基类的私有成员不可访问
3.多重继承与多接口继承
A.多重继承可以看成是C++中单继承的扩展,派生类与每个基类之间的关系仍可看作是一个单继承。
如果想使公共的基类只产生一个拷贝,则可以把它声明为虚基类。
B.多接口继承是C#中单继承的扩展,派生类只有一个基类(抽象类)但有多个接口实现方法,继承时排列顺序是先抽象类后接口
§5.多态与重载
1.函数与运算符重载
A.静态联编函数重载是指同名函数的不同实现版本,在定义重载函数时,相同函数名的参数个数或类型必须相异(否则,出现二义性)
B.派生类对象访问同名成员函数的支配规则:
1其它函数访问对象成员时,先访问本类对象中的同名成员
2如果本类对象中没有该同名成员,则进而访问该对象直接基类的同名成员
3如果该对象的直接基类中仍然没有该同名成员,则不断上溯至该对象的间接基类中寻找,直至找到为止
4如仍找不到,则出现编译错误
C.运算符重载使用同一个运算符作用于不同类型的数据产生不同的行为,实质是函数重载。
重载规则:
1重载的功能应与原有功能类似
2操作数必须是定义它的类的对象
3不能改变运算符的操作个数
4不能改变运算符的优先级和结合特性
5不能改变对预定义类型数据的操作方式
2.虚函数
虚函数允许函数调用与函数体之间的联系在运行时才建立,即在运行时才决定如何动作,也就是所谓的动态联编。
静态联编时,基类中的指针或引用不可调用派生类函数;动态联编时,基类中的指针或引用可以调用派生类中的虚函数。
在派生类中重写虚函数实现版本时,其函数原型(包括返回类型、函数名、参数个数、参数类型、参数顺序等)都必须与基类中的原型完全相同。
3.纯虚函数和抽象类
A.纯虚函数是一个在基类中说明的虚函数,但它在基类中没有定义。
纯虚函数是虚函数的特殊形式,对它的调用也是通过动态联编来实现的,不同的是纯虚函数在基类中完全是空的,调用的总是派生类中的定义。
B.抽象类的使用规定:
1抽象类只能作为其它类的基类使用,不能建立抽象类对象,其纯虚函数的实现由派生类给出
2抽象类不能用作参数类型、返回类型或显示转换的类型
3不允许从具体类(不包含纯虚函数的普通类)派生抽象类
4可以声明指向抽象类的指针或引用,该指针或引用可以指向其派生类
5C++抽象类可以继承(因为纯虚函数可以继承),C#抽象类不能继承(因为抽象方法不能继承)
6抽象类中可以定义普通成员函数或虚函数,而且可以通过派生类对象来调用不是纯虚函数的函数
§6.构造函数、析构函数与成员初始化
1.构造函数
A.类中地位:
类中的特殊成员函数。
一般提到成员函数时,不包括构造函数。
B.定义格式:
①类内
类名(形参列表):
成员初始化列表
{………}
②C++类外
类名:
:
类名(形参列表):
成员初始化列表
{………}
C.特点:
①名字与类名相同,但没有返回值
②可以有零个至多个参数
③可以重载
④不能继承
D.基本功能:
①将对象的非静态数据初始化(静态数据、引用和子对象不能通过构造函数初始化)。
②将初始值从派生类传递给对象中基类部分的数据成员。
③必要时向堆申请空间(如带缺省参数的构造函数初始化对象数组)。
E.调用顺序:
①C++:
先虚基类祖先的客人,再虚基类祖先,再非虚基类祖先的客人和非虚基类祖先,然后自己的客人,最后自己。
例如:
classA{…};
classB:
virtualpublicA{…};
classC:
virtualpublicA{…};
classM{…};
classP{…};
classD:
publicB,publicC
{…
public:
D(……):
B(…),C(…),A(…),part(…),member(…)
{……}//构造函数
Mmember;
Ppart;
…
};
voidmain()
{
Ddd;
…
}
类定义中蓝色字体决定调用顺序
构造函数调用顺序为:
ABCMPD
应该注意:
a.在同一层中,祖先的构造函数中先调用虚基类的。
b.客人构造函数的调用顺序决定于它们在类中的声明顺序,和初始化列表中的顺序无关。
例如:
classD:
publicB,publicC
{…
public:
D(……):
B(…),C(…),A(…),part(…),member(…)
{……}//构造函数
Mmember;
Ppart;
…
};
先调用classM(member)的构造函数,再调用classP(part)的构造函数。
而和part(…),member(…)的顺序无关。
②C#:
先自己的客人,再祖先的客人,然后祖先,最后自己。
例如:
classA
{…
MmA=newM();
…
};
classB:
A
{…
MmB=newM();
…
};
classC:
B
{…
MmC=newM();
…
};
classM{…};
voidmain()
{
Cobj=newC();
…
}
类定义中蓝色字体决定调用顺序
构造函数调用顺序为:
mCmBmAABC
2.析构函数
A.类中地位:
类中的特殊成员函数。
一般提到成员函数时,不包括析构函数。
B.主要功能:
①如构造函数曾经向堆申请空间,则析构函数调用delete函数(C++)或Dispose()方法将空间退还给堆。
②必要时析构函数应能为虚函数。
C.调用顺序:
①C++:
与构造函数调用顺序正好相反。
②C#:
先客人,后家族,且分别与构造函数调用顺序相反。
3.成员初始化
A.类成员的初始化顺序
①C++:
基类对象成员初始化基类成员变量初始化派生类对象成员初始化派生类成员变量初始化
②C#:
派生类静态成员变量初始化派生类实例变量初始化基类静态成员变量初始化基类实例变量初始化基类成员变量初始化派生类成员变量初始化
B.成员初始化列表
①主要用于向直接基类(包括C++虚基类)传递参数
②成员初始化列表的顺序可任意安排
③成员初始化列表中的参数(称传递参数)和直接基类(包括C++虚基类)中构造函数的形参必须两同:
数量相同、类型相同(顺序相同),但传递参数和基类形参的名称可同可不同(一般都不同)
④C++中无虚基类时不准越级传递参数;有虚基类时必须越级直接传递参数给虚基类
以§4.5参数传递的习惯(规则)中的[例4-17-2]五层派生类中的虚基类为例:
//vir_cls9.cpp
//Initializationofargumentofvirtualbaseoffivelevelsofderivedclasses
#include
classB
{public:
B(inti){…}…};
classV:
publicB
{public:
V(inti,intj):
B(i){…}…};
classX:
virtualpublicV
{public:
X(inti,intj):
V(i,j){…}…};
classY:
virtualpublicV
{public:
Y(inti,intj):
V(i,j){…}…};
classW:
publicX,publicY
{public:
W(inta,intb,intc,intd,inte,intf):
X(a,b),Y(c,d),V(e,f){…};
classZ:
publicW
{public:
Z(inta,intb,intc,intd,inte,intf,intg):
W(b,c,d,e,f,g),V(f,g){…}…};
voidmain(){Zobj(1,2,3,4,5,6,7);…}
/*Result:
67(nor6nor2nor4)3521*/
DAG
B:
:
B(i)
V:
:
V(i,j):
B(i)
X:
:
X(i,j):
V(i,j)Y:
:
Y(i,j):
V(i,j)
W:
:
W(a,b,c,d,e,f):
X(a,b),Y(c,d),V(e,f)
Z:
:
Z(a,b,c,d,e,f,g):
W(b,c,d,e,f,g),V(f,g)
其中
表示派生关系
表示参数传递
此处间接基类V是虚基类,则Z、W必须直接向V传递参数。
又如:
D:
:
D(inta,intb,intc,intd):
B(a),C(b),member(c),part(d){…}
此处客人必须用对象名。
因间接基类A不是虚基类,所以D不能直接向其间接基类A传递参数。
§7.DAG、内存存储内容和访问控制表
7-1.DAG(有向无环图)
用于表示继承关系。
应注意C++中有无虚基类,如无虚基类则可能有多个同名间接基类。
例如下图中的classbase:
DAG
base
base
publicpublic
derive2
derive1
publicpublic
grand
多继承类型
对象内存
存储内容
DAG
无虚基类
两个以上相同基类数据
有虚基类
只有一个相同基类数据
不同DAG决定了不同的内存存储内容和不同的构造函数调用顺序。
也可以附加箭头来表示参数传递关系和构造函数调用顺序。
答题时,如未提出要表示参数传递关系和构造函数调用顺序,则可不画。
7-2.内存存储内容
1.建立一个对象时,系统只在该对象的内存存储区(栈区或堆区)中存储其非静态数据成员而不存储对象的静态数据和成员函数,因静态数据和成员函数是同类的所有对象所共享的。
2.内存存储内容既包括基类的也包括派生类的所有非静态数据成员,这些非静态数据成员中不管是否能够访问,都一律存放在内存存储区内。
排列顺序是基类在前,派生类在后且C++取决于派生类中各数据的声明顺序,C#先客人再祖先后自己。
*3.如果有虚函数,则还包括该内存存储区内的虚指针VPTR(VPTR一般位于内存存储区的首部);数据区内的vtable(虚地址表)(其中存放各虚函数的地址);以及代码区内的各虚函数。
这三个区应该区别开。
基类和各派生类的各个vtable中各虚函数地址的存放顺序必须完全相同。
*4.当对象内存存储区大于4个字节时,内存存储区以4个字节为增量。
如下:
//obj_size_2.cpp
//ToseehowmanybytesclassAoccupies
//thereseemstobea4-byteboundary
#include
classA
{
inti;
charch1;
};
voidmain()
{
cout<<"sizeof(A):
"<}
/*Results:
sizeof(A):
8*/
应注意:
存放在栈区内的非静态数据、存放在堆区内的数据(用new分配的)和存放在数据区内的静态数据和全局变量这三者不应放在一起。
(区名记不住不要紧)
为能准确地确定内存存储内容,最好先画出DAG。
7-3.访问控制表
见第四章§4.2“派生类成员函数及其访问控制”
类成员的访问控制符:
缺省值为私有,可声明为公有或保护;
私有(private)成员:
只能由同一类的成员函数和友员函数访问,不准派生类成员函数和外部函数访问;
公有(public)成员:
可由任何函数访问;
保护(protected)成员:
除与私有成员相同外,还允许派生类成员函数访问,但不准外部函数访问。
[重要注解]此处外部函数系指主程序(主函数)、其他类的成员函数和普通函数等。
访属
问性
权
继承
publlic
protected
private
public
public
protected
不可访问
C++
private
private
private
不可访问
protected
protected
protected
不可访问
其中“属性”即基类成员的访问属性b_am,“继承”即派生类的继承方式inh_m,“访问权”即访问权限der_am,也即基类成员的访问权限在派生类中的映射。
结合[例4-4]程序来分析三者关系
classBASE
{
intx1;
publicBASE(inta){x1=a;}
publicinc(){returnx1;}
};
classDERIVED:
BASE
{
intx2;
publicDERIVED(inta,intb):
base(a){x2=b;}
};
classGRAND:
DERIVED
{
intx3;
publicGRAND(inta,intb,intc):
base(a,b){x3=c;}
};
privatevoidForm1_Load(objectsender,EventArgse)
{
BASEp=newBASE(5);
DERIVEDder=newDERIVED(7,11);
GRANDgr=newGRAND(3,6,9);
}
DAG
BASE
public
DERIVED
public
GRAND
对象p对象der对象gr
d2.x3=9
d1.x2=11
p.x1=5
访问控制表
classname
public
private
inh
classBASE
inc()
x1
classDERIVED:
base
x2
inc()
classGRAND:
base
x3
制作访问控制表的要点
1.既有数据(包括静态数据)又有成员函数。
2.既包括本派生类的数据和成员函数,又包括直接和间接基类的数据和成员函数。
但以上两项只包括允许访问的数据和成员函数。
3.成员函数只需标出函数名加()即可,不必填入形参,最多写(…)。
4.虚函数只需写本派生类的一个,因this指针只指向这一个。
5.直接和间接基类的成员函数中如有同名者,必须加上类名以避免二义性,例如D2:
:
display()。
访问控制表和内存存储内容的区别:
派生类的访问控制表:
1.只显示符合访问权限的允许访问的数据成员(包括静态数据)和成员函数。
2.所有这些允许访问的数据成员和成员函数既包括该派生类的又包括各基类(直接基类和间接基类)的。
3.但不包括虚指针。
虚函数也可只显示本派生类的。
派生类对象的内存存储内容:
1.既包括该派生类的又包括各基类(直接基类和间接基类)的所有非静态数据(无论是否允许访问都应包括在内)。
2.如果有虚函数,则还包括对象内存存储区内的虚指针VPTR;数据区内的vtable(虚地址表);以及代码区内的各虚函数。
这三个区应该区别开。
3.不包括任何静态数据和成员函数。
§8.C++虚基类
(一)如何识别虚基类?
虚基类的定义不出现在它自己的类定义首部,而因它主要用于多继承的情况,所以virtual关键词出现在至少两个或更多个它的的派生类首部,例如:
classA{……};
classB1:
virtualpublicA{……};
classB2:
virtualpublicA{……};
此时A就是虚基类。
(二)虚基类的用途:
A.在多继承中,主要用于避免二义性。
[例1]没有虚基类的例子
#include
classA
{public:
inta;};
classB1:
publicA
{intb1;};
classB2:
publicA
{intb2;};
classC:
publicB1,publicB2
{intc;};
voidmain()
{Cobj;}
DAG
A
A
publicpublic
B2
B1
publicpublic
C
构造函数调用顺序:
AB1AB2C
classC对象c的存储内容
c.A:
:
a(B1)
c.B1:
:
b1
c.A:
:
a(B2)
c.B2:
:
b2
c