C++知识要点

转自:微点阅读  https://www.weidianyuedu.com/content/5417775689104.html

c++引言

c++程序的基本框架

(1) 结构化程序设计框架:基本组成单元是函数 (2) 面向对象程序设计框架:基本组成单元是类

c++基础知识

1、 变量命名规则及其变量初始化

例如:double rate=0.07;或double rate(0.07); 2、 基本输入输出

通过标准库中的输入/输出流对象cin和cout来完成 3、 数据类型和表达式

(1) 逻辑型bool,取值范围为true和false(c中没有该数据类型)

字符型char 整型int

浮点型float和double

(2) 算术运算符和算术表达式 注意:++、--、%运算符 (3) 关系运算符和关系表达式 注意:==和=之间的区别 (4) 逻辑运算符(!、&&、||)和逻辑表达式 4、 选择语句与循环语句

(1) 选择语句:if语句和switch语句

(2) 循环语句:for语句、while语句和do-while语句

注:while语句和do-while语句循环体一定要改变循环控制变量

例1、bool类型 #include using namespace std; void main() {

bool a; cin>>a; cout<

例2、:表达式计算

(1)(float)(a+b)/2-(int)x%(int)y 设a=2,b=3,x=3.5,y=2.5 答:

按优先级先计算强制类型转换:(float)(a+b)/2=5.0 /2=2.5

(int)x%(int)y=(int)3.5%(int)2.5=3%2=1 最后计算(float)(a+b)/2-(int)x%(int)y=2.5-1=1.5 (2)"a"+x%3+5/2-"\\24" 设x=8 "a"+x%3+5/2-"\\24"=97+8%3+5/2-24=97+2+2-20=81 ‘\\24’为八进制,‘\\24’=024(八进制)=2*8+4(十进制)=20(十进制) "a"的AScII码为97 例3、

#include void main(void) { int i,j,m,n; i=8;j=10;

m=++i;n=j++; //i=9 m=9 ,n=10 j=11 cout<

例4、逗号表达式。设a、b、c的值分别为5、8、9;指出分别运算下列表达式后x、y的值。 (1)y=(a+b,b+c,c+a) (2)x=a,y=x+b 解:

(1)y=(a+b,b+c,c+a) =(5+8,8+9,9+5)=(13,17,14)=14 y=14

(2)x=a, y=x+b = (x=5,y=x+b)=(5,y=5+8)=(5,y=13)=13 x=5, y=13 例5、短路求值

#include void main(void)

{ int w=3,x=10,z=7; char ch="D"; bool s;

s=w++||++z;

cout<

例6、从键盘输入一个三位数abc,从左到右用a、b、c表示各位的数字,现要求依次输出从右到左的各位数字,即输出另一个三位数cba,例如:输入123,输出321,试设计程序。

例7、写出下列程序的运行结果 #include void main(void) { int n=4; while (--n)

cout<

函数

1、 函数基本知识

(1) 数据类型转换 如:(double)或static_cast (2) 函数定义 注:函数只能返回一个值,不能返回多个值 (3) 函数调用 调用方法为语句调用、表达式调用和参数调用 (4) 注意变量作用域与生存周期

Type_returned Function_name(Parameter_List);

Type_returned Function_name(Parameter_List) {

//code to make the function work }

Argument

2、 函数参数

参数的传递方式: (1) 传值

传值是将实参值的副本传递(拷贝)给被调用函数的形参 (2) 传指针和传引用

传指针属于显示传递地址,因为传递的是地址,而变量的地址是唯一的,所以操作会影响到实参

引用的本质也是指针,是c++增加的一个更安全的操作。与传值相比,它们的优点是效率高,传指针和传引用不用进行复制操作,不用把实参的值复制一份给形参 注意:引用的概念 3、 函数重载

重载规则:

(1) 函数重载必须参数类型不一样或参数个数不一样 (2) 能依靠返回值不一样来区别重载函数

例1、参数传递过程。写出下列程序的运行结果 #include void modify(int x,int y)

{ cout<<\ x=x+3; y=y+4;

cout<<\}

void main(void) { int a,b; a=1;b=2;

cout<<\ modify(a,b);

cout<<\}

例2、递归调用过程 #include int fac(int n) { int z;

if (n>0)

z=n*fac(n-2); else z=1; return z; }

void main(void) { int x=7,y; y=fac(x);

cout<

例3、静态变量

#include void main(void) { int i;

void add1(void),add2(void); for (i=0;i<3;i++) { add1(); add2(); cout<

void add1(void) { int x=0; x++;

cout<

void add2(void) { static int x=0; x++;

cout<

例4、编写一个函数判断一个整数是否为素数。在主函数中输入一个整数,输出该整数是否为素数的信息。

#include #include #include int prime(int a) { int k,i; k=sqrt(a);

for (i=2;i<=k;i++) //判断a是否是素数

if (a%i==0) break;

if (i>k) //若i>k,则i为素数 return 1; else

return 0; }

void main(void) { int a,b;

cout<<\请输入一个整数:\ cin>>a; b=prime(a); if (b==1)

cout<

cout<

例5、编写两个函数,分别求两个整数m、n的最大公约数和最小公倍数。在主函数中输入两个整数,分别调用这两个函数求得结果并输出。求两个整数m、n的最大公约数和最小公倍数的算法提示如下:

(1)将m、n中的最大数赋给变量a,最小数赋给变量b。

(2)用大数a除以小数b,若余数c为0,则余数c为最大公约数,否则进行(3)。 (3)将小数b赋给a,余数c赋给b,再进行(2),直到余数等于0为止。 (4)最小公倍数=(m*n)/最大公约数。

例如:求20与14的最大公约数方法:20=6,14%6=2,6%2=0,则2为20与14的最大公约数。最小公倍数=20*14/2=140。 # include int gcd(int m,int n) { int r;

while (m%n!=0) { r=m%n; m=n; n=r; }

return n; }

int lcm(int m,int n) { int lcm; lcm=m*n;

lcm=lcm/gcd(m,n); return lcm; }

void main(void)

{ int m,n;

cout<<\ cin>>m>>n;

cout<<\ cout<<\}

例6:

#include using namespace std; int &fun(int &temp) {temp+=10; return temp; }

int main() {int a=0;

int &b=fun(a);

cout<<\ b=20;

cout<<\return 0; }

数组与指针

1、 一维数组的定义、初始化和数组元素的访问

注意:数组元素的下标是从0开始 2、 数组与函数

(1) 数组元素做函数实参微点阅读  https://www.weidianyuedu.com

(2) 数组名做函数参数(形参、实参) 3、 多维数组

注意:二维数组的定义、初始化和数组元素的访问,每一维的下标均从0开始 4、 指针

(1) 定义指针变量的形式

(2) 指针使用两种运算符&和*

&用于返回操作对象的内存地址,操作对象通常为一个变量名

*用于返回操作数所指对象的值,该运算符要求其操作对象为一个指针

(3) 指针和地址

使用一个指针指向一个具体对象的方法如下: ① 使用new运算符给指针分配一个具体空间 ② 将另一个同类项的指针赋给它以获得值 ③ 通过&运算符指向某个对象

5、 动态数组

(1) 使用new获得动态内存空间

格式为:<指针>=new <类型> [<元素个数>]; (2) 使用delete释放动态内存空间

格式为:delete []<指针>

例1、数组参数的调用。写出程序的运行结果 #include #include void main(void)

{ char a[]=\ int n;

void fun(char s[],int k); n=strlen(a); fun(a,n);

cout<

void fun(char s[],int k) { int x,y; char c; x=0;

for (y=k-1;x

{ c=s[y]; s[y]=s[x]; s[x]=c; x++; } }

例2、编写一个排序函数用选择法对一批整数按从大到小的次序进行排序。在主函数内输入数据,调用排序函数对数据排序,输出排序结果。 解:

# include # define n 5

void sort1(int a[]) { int i,j,k,temp;

for (i=0;i

for (j=i+1;j

if (a[k]

{ temp=a[i];a[i]=a[k];a[k]=temp;} } }

void main(void)

{ int a[n],i;

cout<<\ for (i=0;i>a[i]; sort1(a);

for (i=0;i

cout<

例3、编写两个名为max的重载函数,分别实现求两个整数和两个实数中的大数。 解:

# include float max(float a,float b) { return a>b?a:b; }

int max(int a,int b) { return a>b?a:b; }

void main(void) { int a,b; float x,y ;

cout<<\、b:\ cin>>a>>b;

cout<<\ cout<<\、y:\ cin>>x>>y;

cout<<\}

例4 叙述二维数组a的行地址、行首地址、元素地址的概念、作用及表示方法,写出元素a[i][j]值的表示方法。 答:

(1)二维数组a的第i行首地址是第i行第0列元素地址&a[i][0]。 有三种表示方式:&a[i][0]、a[i]、*(a+i)、&a[i][0]。

(2)二维数组a的第i行的行地址是用于指向一维数组指针变量的地址。 有二种表示方式:a+i、&a[i]

(3)二维数组a的元素a[i][j]的地址为该元素在内存中的地址, 有四种表示方式:a[i]+j 、*(a+i)+j、&a[i][0]+j、&a[i][j]

(4)元素a[i][j]的值的表示方式:*(a[i]+j) 、 *(*(a+i)+j)、*(&a[i][0]+j)、a[i][j]

例5、对字符串指针变量,能否用cin、cout对其输入/输出字符串?能否对字符串指针变量进行赋值运算?字符串指针变量能否作为字符串处理函数的参数?

答:

对字符串指针变量,能用cout对其输出字符串,但不能用cin对其输入字符串; 能对字符串指针变量进行赋值运算;

字符串指针变量能作为字符串处理函数的参数。

例6、读下列程序,并写出程序运行结果。 (1)

# include void main (void)

{ float x=1.5,y=2.5,z; float *px,*py; px=&x; py=&y;

z= * px + *py;

cout<<\}

(2)

# include void main( void)

{ int a[5]={10,20,30,40,50};

int *p=&a[0]; //p指向a[0]

p++; //p指向a[1] cout<< *p<<"\\t"; //输出a[1]=20 p+=3; //p指向a[4] cout<< *p<<"\\t"; //输出a[4]=50

cout<< *p――<<"\\t"; //先输出a[4]=50,后p指向a[3] cout<<++ *p<<"\\n"; //先对a[3]内容加1,后输出a[3]=41 }

(3)# include void f(int *a,int b) { int t=*a;*a=b;b=t;} void main(void) { int x=10,y=20;

cout<

cout<

例7、用指针变量编写下列字符串处理函数:

(1)字符串拷贝函数,void str_cpy( char *p1,char *p2){函数体} (2)字符串比较函数,int str_cmp( char *p1,*char *p2) {函数体}

(3)取字符串长度函数,int str_len( char *p){函数体}

在主函数中输入两个字符串,对这两个字符串进行比较,并输出比较结果。然后将第一个字符串拷贝到第二个字符串,输出拷贝后的字符串及其长度。 解:

# include

int str_cmp(char *p1,char *p2) { while(*p1==*p2) {p1++; p2++; }

if (*p1>*p2) return 1; else if (*p1==*p2) return 0; else

return -1; }

void str_cpy(char *p1,char *p2) { while(*p1!=0)

*p2++=*p1++; *p1=0; }

int str_len(char *p) { int length=0; while(*p!=0) { p++;

length++; }

return length; }

void main(void)

{ char s1[20],s2[40]; cout<<\ cin>>s1;

cout<<\ cin>>s2;

if (str_cmp(s1,s2)==1)

cout<<\ else if (str_cmp(s1,s2)== -1)

cout<<\ else

cout<<\ str_cpy(s1,s2);

cout<<\

cout<<\}

例8、设计返回指针值的函数,将输入的一个字符串按逆向输出。 解:

# include # include

char *reverse(char *p, int n) { int i=0,j=n-1; char temp; while(i

{ temp=*(p+i); *(p+i)=*(p+j); *(p+j)=temp; i++; j--; }

return p; }

void main(void) { char s[30]; int l;

cout<<\ cin>>s; l=strlen(s);

cout<

例、有哪三种方法来定义const 型指针?这三种const指针限制哪些变量的值不能改变? 答:

const 型指针

有三种方法来定义const 型指针: const <类型> *<指针变量名>;

定义指针变量所指数据值不能改变,但指针变量值可以改变。 <类型> * const <指针变量名>;

定义指针变量值不能改变,但指针变量所指数据值可以改变。 const <类型> * const <指针变量名>;

指针变量值与指针变量所指数据值都不能改变。

输入输出流

1、 c++流的概念

(1) c++流的体系结构

与c++流相关的头文件:

① iostream——要使用cin、cout的预定义流对象进行针对标准设备的I/o操作,

必须包含此文件 ② fstream——要使用文件流对象进行针对磁盘文件的I/o操作,必须包含此文件 ③ iomanip——要使用setw、fixed等操作符,必须包含此文件

(2) 预定义流对象

cin、cout等

(3) 提取运算符和插入运算符 >>与<< (4) 输入输出的格式控制

① 格式控制标志与格式控制 ② 输入输出宽度的控制 ③ 浮点数输出格式的控制 ④ 输出精度的控制 ⑤ 对齐方式的控制 ⑥ 小数点处理方式的控制 ⑦ 填充字符设置

(5) cin.get()、cin.put()和cin.getline()的应用

注意:一般情况下,cin自动跳过输入的空格,如果你要把键盘上输入的每个字符

包括空格和回车键都作为一个输入字符给字符型变量时,必须使用函数cin.get()。 2、 文件流

ifstream是文件输入流,ofstream是文件输出流,fstream是文件输入输出流类 (1) 文件流的建立——文件流的成员函数open()

① 在建立文件流对象的同时打开文件 ② 先建立文件流对象,再在适当的时候打开文件 注意:文件的打开模式

(2) 文件流的关闭——文件流的成员函数close() (3) 文件流状态的判别

① fail()——刚进行的操作失败时返回true,否则返回false ② eof()——若达到文件尾返回true,否则返回false

例1、叙述文本文件的使用过程。 答:

文本文件的具体使用步骤如下: (1) 用文件流类定义文件流对象, (2) 打开文件

①用成员函数open()打开文件 ②用构造函数打开文件 (3) 读/写文件

①用提取运算符“>>”与插入运算符“<<”对文件进行读与写操作; ②用成员函数get()、getline()与put() 对文件读写。 (4)关闭文件

用成员函数close关闭文件。

例2、使用成员函数打开文本文件,并将源文件中内容添加到目的文件的尾部。 解:

# include

# include # include void main(void)

{ char fname1[256],fname2[256]; char buff[300];

cout<<\输入源文件名:\ cin>>fname1;

cout<<\输入目的文件名:\ cin>>fname2;

fstream infile,outfile;

infile.open(fname1,ios::in | ios::nocreate); outfile.open(fname2,ios::app); if (!infile)

{ cout<<\源文件不存在,不能打开源文件!\ exit(1); }

if (!outfile)

{ cout<<\目标文件不存在,不能打开目标文件!\ exit(2); }

while (infile.getline(buff,300)) //从源文件中读一行字符到缓冲区;

outfile<

例3、从文本文件中读取数据输入一维数组,对一维数组元素进行升序排列。再存入另一个文本文件中。 解:

# include # include # include # define n 5 void main(void) { float a[n],temp; int i,j,k;

char fname1[256],fname2[256]; cout<<\输入源文件名:\ cin>>fname1; ifstream infile;

infile.open(fname1); if (!infile)

{ cout<<\不能打开目的文件:\ exit(1);

}

cout<<\输入目标文件名:\ cin>>fname2; ofstream outfile;

outfile.open(fname2); if (!outfile)

{ cout<<\不能打开目的文件:\ exit(1); }

for(i=0;i>a[i]; for(i=0;i

for(j=i+1;ja[j]) k=j; if (k>i)

{ temp=a[i];a[i]=a[k];a[k]=temp;} }

for (i=0;i

cout< outfile<String与Vector

1、 字符数组

(1) 字符数组的初始化

注意:用字符串对字符数组进行初始化时,编译程序以’\\0’为结束这个数组的标志 (2) 常用字符串函数(注意:这些函数包含在头文件cstring中)

① strcpy函数 ② strcat函数 ③ strcmp函数 ④ strlen函数 2、 string类型

(1) 在使用string数据类型前,需要包含如下格式:

#include using namespace std;

(2) string类型的输入

cin自动跳过输入的空格,若读入的串还有空格等字符,则使用getline函数

(3) string类的常用成员函数

① 构造函数

② 字符操作 ③ 赋值与连接 ④ 比较 ⑤ 查找 3、 vector 容器向量(vector)是一个类模板。标准库vector类型使用需要的头文件:#include 。vector 是一个类模板,不是一种数据类型,vector是一种数据类型。Vector的存储空间是连续的

(1) 定义与初始化

vector< typename > v1; //默认v1为空

vector< typename > v4(n); //v4含有n个值为0的元素 (2) vector对象的几种操作

① v.push_back(t) 在容器的最后添加一个值为t的数据,容器的size变大。 ② v.size() 返回容器中数据的个数 ③ v[n] 返回v中位置为n的元素

④ v.capacity()返回容器在它已经分配的内存中可以容纳多少元素 ⑤ v.resize(n)强制把容器改为容纳n个元素。

⑥ v.reserve(n)强制容器把它的容量改为至少n,提供的n不小于当前大小。

类的定义

1、 结构体

(1) 结构体类型的声明

(2) 结构体变量的定义与初始化 (3) 结构体变量的引用(“.”是成员分量运算符) 2、 类

(1) 类的定义

类的定义可以分为说明部分和实现部分。类定义的一般格式如下: //类说明部分 class <类名> {

public:

<成员函数或数据成员的说明> //公有成员,外部接口 protected:

<成员函数或数据成员的说明> //保护成员 private:

<成员函数或数据成员的说明> //私有成员 };

//类的实现部分

<各个成员函数的实现> (2) 类成员的控制访问

类中提供了3种访问控制权: ① 公有(public):公有类型定义了类的外部接口,任何一个外部访问都必须通过外部

接口进行

② 私有(private):私有类型的成员只允许本类的成员函数访问,来自类外部的任何访

问都是非法的 ③ 保护(protected):保护类型介于公有类型和私有类型之间,在继承和派生时可以体

现出其特点

(3) 类的数据成员

注意:类中的数据成员可以是任意类型,也可以是对象。但是要注意,只有另外一个类的对象,才可以作为该类的成员,即作为该类的成员对象存在。自身类的对象是不可以作为自身类的成员存在的,但自身类的指针可以。 (4) 类的成员函数

类的成员函数描述类所表达的问题的行为。

成员函数的定义可以直接定义在类的内部,即该函数作为内联函数; 成员函数的定义也可以在类的外部定义,注意它的格式:

<返回类型> <类名>::<成员函数名> (<参数表>) { <函数体> }

注意:若形参为const,则该形参值不能变 (5) 构造函数

构造函数的作用是在对象被创建时利用特定的值构造对象,将对象初始化为一种特定的状态,使该对象具有区别于其他对象的特征。它具有如下的特性: ① 构造函数的名字必须与类的名相同 ② 构造函数不指定返回类型 ③ 构造函数可以重载 ④ 在创建对象时,系统自动调用构造函数 (6) 析构函数

用来完成对象被删除前的一些清理工作,它具有如下的特性: ① 析构函数的名是在类名前面加上~符号 ② 析构函数不指定返回类型 ③ 析构函数没有参数,一个类中只能定义一个析构函数 ④ 在撤销对象时,系统自动调用析构函数 (7) 拷贝构造函数

它用一个已知的对象初始化一个正在创建的同类对象,它的一般格式如下: <类名>::<类名> (const <类名>&<引用对象名>) { //拷贝构造函数体 }

拷贝构造函数在以下情况会被调用: ① 用类的一个已知的对象去初始化该类的另一个正在创建的对象 ② 采用传值调用方式时,对象作为函数实参传递给函数形参 ③ 对象作为函数返回值

3、 对象的定义、生存周期和this指针 (1) 对象的定义 (2) 对象的成员

定义了对象后,可以使用“.”和“->”运算符访问对象的成员。其中,“.”运算符适用

于一般对象和引用对象,“->”运算符适用于指针对象 (3) 对象的生存周期

全局对象、静态对象和局部对象 (4) this指针

this指针是一个隐含的指针,它隐含于每个类的非静态成员函数中,它明确的表示出了成员函数当前操作的数据所属的对象 4、 静态成员和常成员 (1) 静态成员

对于类中的非静态成员,每个类对象都拥有一个拷贝(副本),即每个对象的同名数据成员可以分别存储不同的数值。

静态成员的特性是不管类创建了多少个对象,它的静态成员都只有一个拷贝(副本),这个副本被所有属于这个类的对象共有 ① 静态数据成员

声明时要使用关键字static,静态数据成员初始化在类体外进行,一般格式: <数据类型><类名>::<静态数据成员名>=<初始值>; ② 静态成员函数

使用static关键字声明的成员函数就是静态成员函数。静态成员函数属于整个类而不属于类中的某个对象

静态成员函数只能直接访问类中说明的静态成员

5、 常成员 (1) 常对象

使用const关键字修饰的对象 (2) 常成员函数

使用const关键字说明的成员函数,其格式: <返回类型> <成员函数名> (<参数表>) const

其重载原则是,常对象调用常成员函数,一般对象调用一般成员函数 (3) 常数据成员

使用const说明的数据成员 6、 抽象数据类型的含义和特点

例1、用学生档案结构体定义一个学生的档案结构变量。用初始化方式输入学生档案内容,并输出学生档案。 解:

# include struct student { int no ;

char name[8]; char sex; int age;

char addr[20]; int post;

char photo[15]; };

void main(void)

{ student s={101,\ cout<<\学号:\ cout<<\姓名:\ cout<<\性别:\ cout<<\年龄:\ cout<<\地址:\ cout<<\邮编:\ cout<<\电话:\}

例2、什么叫类?什么叫对象?

答:

类由描述某类事物的数据(数据成员)及处理数据的函数(成员函数)组成的导出数据类型,用类定义的变量称为对象。

例3、叙述公有、私有、保护成员在类内、外的访问权限。

答:

public(公有成员) 公有数据成员允许类内或类外函数访问, 成员的 公有成员函数允许在类内或类外调用。 访问权限 private(私有成员) 私有数据成员只允许类内函数访问, 私有成员函数只允许在类内调用。

protected(保护成员) 保护数据成员只允许类或其子类中函数访问, 保护成员函数允许在类内或其子类中调用。

例4、构造函数的作用是什么?构造函数的名称与类型有何特点?何时调用构造函数? 答:

构造函数用于对象数据成员的初始化。

构造函数名必须与类名相同,且无返回类型。

用类定义对象时调用构造函数对数据成员进行初始化。

例5、构造函数可以分为哪三类?其形参有何区别?分别初始化何种对象? 答:

构造函数可以分为:

(1)有参构造函数:形参可以是任意类型的变量,用于初始化有实参的对象; (2)无参构造函数:没有形参,用于初始化无实参的对象;

(3)拷贝构造函数:形参必须为类的对象的引用, 初始化时用于将一个已存在对象的数据拷贝到新建对象中,即用于初始化实参为已存在对象的对象。

例6、叙述定义对象时,调用构造函数的过程及参数的传送过程? 答:

用类定义一个对象时,系统先为其分配内存空间,然后调用构造函数对数据成员进行初始化。系统调用构造函数前,首先判断定义对象是否有实参。

若无实参,则调用无参构造函数,对新建对象的数据成员进行初始化。

若有实参,且实参不是对象,则调用带参构造函数,先将实参传送给形参,然后通过构造函数体实现对新建对象数据成员的初始化。

若实参为已存在的对象,则调用拷贝构造函数,由于拷贝构造函数的形参为对象的引用,所以实参对象与形参对象占用相同的内存空间,具有相同的数据内容,因此通过拷贝构造函数可实现新建对象数据成员的初始化。

例7、析构函数的作用是什么?析构函数的名称与类型有何特点?何时调用析构函数? 答:

(1)析构函数的作用是回收对象所占用的内存空间。

(2)析构函数的名称必须与类名相同,且析构函数名前必须加”~”。析构函数无参数无返回类型,析构函数不允许重载,即析构函数是唯一的;

(3)当对象结束其生命期,系统调用析构函数,回收对象所占用的内存空间。

例8、用new运算符动态建立对象与用delete运算符撤消动态建立对象时,系统如何调用构造函数与析构函数? 答:

当用new运算符动态建立对象时,系统 (1)为对象分配内存空间

(2)调用构造函数初始化对象的数据成员

先将实参传送给形参,然后由构造函数体完成对数据成员的初始化工作。 (3)将对象起始地址返回给指针变量

用new动态分配的内存空间必须用delete回收,在执行delete 语句时,将调用析构函数回收对象占用的内存空间。

例9、什么是this指针? 答:

用类定义一个对象时,系统会自动建立一个指向该对象的指针变量,该指针变量称this指针,this指针变量指向所定义的对象。

例10、定义一个学生成绩类Score,描述学生成绩的私有数据成员为学号(no)、姓名(name[8])、数学(Math)、物理(Phi)、数据结构(Data)、总分(Sum)。定义能输入学生成绩的公有成员函数Input(),能计算学生总分的成员函数Sum(),能显示学生成绩的成员函数Show()。在主函数中用Score类定义学生成绩对象数组s[5]。用Input()输入学生成绩,用Sum()计算每个学生的总分,最后用Show()显示每个学生的成绩。 解:

# include # include class Score { private: int no;

char name[8];

float Math,Phi,Data,Sum; public:

void Input(int no,char name[],float math,float phi,float data) { no=no;

strcpy(name,name);

Math=math; Phi=phi;

Data=data; }

void output(int &no,char name[],float &math,float &phi,float &data,float &sum)

{ no=no;

strcpy(name,name); math=Math; phi=Phi; data=Data; sum=Sum; }

void Summation(void)

{ Sum=Math+Phi+Data;}

void Show()

{ cout<

void main(void) { int i,no;

char name[8];

float math,phi,data; Score s[5];

cout<<\ for (i=0;i<5;i++) {

cin>>no>>name>>math>>phi>>data; s[i].Input(no,name,math,phi,data); s[i].Summation(); }

cout<<\学号 姓名 数学 物理 数据结构 总分\\n\ for (i=0;i<5;i++) s[i].Show(); }

例11、定义一个复数类complex,复数的实部Real与虚部Image定义为私有数据成员。用复数类定义两个复数对象c1、c2,用 构造函数将c1初始化为 c1=10+20i ,将c2初始化为c2=0+0i。

然后将c1的值赋给c2。最后用公有成员函数Dispaly()显示复数c1与c2 的内容。 解:

# include class complex { private:

float Real,Image; public:

complex(float r,float i) //定义有参构造函数 { Real=r; Image=i; }

complex(complex &c) //定义拷贝构造函数 { Real=c.Real; Image=c.Image; }

complex() //定义无参构造函数 { Real=0; Image=0; }

void Display()

{ cout<

void main(void)

{ complex c1(10,20),c2; c1.Display(); c2.Display(); c2=c1;

c2.Display(); }

例12、

定义一个矩形类Rectangle,矩形的左上角(Left,Top)与右下角坐标(Right,Bottom)定义为保护数据成员。用公有成员函数Diagonal()计算出矩形对角线的长度,公有成员函数Show()显示矩形左上角与右下角坐标。在主函数中用矩形类定义对象r1与r2,r1的初值为(10,10,20,20)。r2右下角坐标的初值用拷贝构造函数将r1右下角坐标值拷贝到r2中,左上角坐标初值为(0,0)。显示矩形r1、r2的左上角与右下角坐标及对角线长度。 解:

# include # include class Rectangle { protected:

float Left,Top;

float Right,Bottom; public:

Rectangle(float l,float t, float r,float b)

{ Left=l;Top=t;

Right=r;Bottom=b; }

Rectangle(Rectangle & R) { Left=0;Top=0;

Right=R.Right;Bottom=R.Bottom; }

float Diagonal()

{ return sqrt((Left-Right)* (Left-Right)+(Top-Bottom)*(Top-Bottom));} void Display()

{ cout<<\

cout<<\ cout<<\} } ;

void main(void)

{ Rectangle r1(10,10,20,20),r2(r1); r1.Display(); r2.Display(); }

例13、

定义一个描述圆柱体的类cylinder,定义圆柱体的底面半径Radius与高High为私有数据成员。用公有成员函数Volume()计算出圆柱体的体积,公有成员函数Show()显示圆柱体的半径、高与体积。在主函数中用new运算符动态建立圆柱体对象,初值为(10,10)。然后调用Show()显示圆柱体的半径、高与体积。最后用delete运算符回收为圆柱体动态分配的存储空间。 解:

# include class cylinder { private:

float Radius,High; public:

cylinder(float r,float h) { Radius=r; High=h; }

float Volume()

{ return Radius*Radius*3.1415*High;} void Show()

{ cout<<\ cout<<\olume()<<"\\n"; } };

void main(void)

{ cylinder *pc=new cylinder(10,10); pc->Volume(); pc->Show(); delete pc; }

例14、

先定义一个能描述平面上一条直线的类Beeline,其私有数据成员为直线两个端点的坐标(X1,Y1,X2,Y2)。在类中定义形参缺省值为0的构造函数,及计算直线长度的公有成员函数Length(),显示直线两个端点坐标的公有成员函数Show()。然后再定义一个能描述平面上三角形的类Triangle,其数据成员为用Beeline定义的对象line1、line2、line3与三角形三条边长l1、l2、l3。在类中定义的构造函数要能对对象成员与边长进行初始化。再定义计算三角形面积的函数Area(),及显示三条边端点坐标及面积的函数Print(),Print()函数中可调用Show()函数显示三条边两端点坐标。

在主函数中定义三角形对象tri(10,10,20,10,20,20),调用Print()函数显示三角形三条边端点坐标及面积。 解:

# include # include class Beeline { private:

float X1,Y1,X2,Y2; public:

Beeline(float x1=0,float y1=0,float x2=0,float y2=0) { X1=x1;Y1=y1;X2=x2;Y2=y2;} float Length()

{ return sqrt((X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2));} void Show()

{ cout<<\ cout<<\ } };

class Triangle { private:

float L1,L2,L3;

Beeline Line1,Line2,Line3; public:

Triangle(float x1,float y1,float x2,float y2,float x3,float y3):Line1(x1,y1,x2,y2),

Line2(x2,y2,x3,y3),Line3(x3,y3,x1,y1)

{ L1=Line1.Length(); L2=Line2.Length(); L3=Line3.Length(); }

float Area() { float s;

s=(L1+L2+L3)/2;

return sqrt(s*(s-L1)*(s-L2)*(s-L3)); }

void Print()

{ Line1.Show(); Line2.Show(); Line3.Show();

cout<<\ } };

void main(void)

{ Triangle tri(10,10,20,10,20,20) ; tri.Print(); }

例15、 写出下列程序的运行结果 # include class Point { private: float X,Y; public:

void SetXY(float x,float y) { X=x; Y=y; }

unsigned Address(void) { return (unsigned) this;} void Show(void)

{ cout<<\ };

void main(void) { Point a,b;

a.SetXY(10.5,10.5); b.SetXY(1.2,5.4); a.Show( ); b.Show( );

cout<<\

cout<<\} 答:

X=10.5 Y=10.5 X=1.2 Y=5.4 a address:6749680 b address:6749672

例10、写出下列程序的执行结果。 # include class Strucf { private:

float a,b,max; public:

Strucf(int x,int y) { a=++x; b=y++; if (a>b) max=a; else max=b;

cout<<\ }

Strucf(float x) { a=x; b=10;

if (a>b) max=a; else max=b;

cout<<\ } Strucf()

{ max=a=b=0; } void Print()

{ cout<<\ cout<<\ }

~Strucf()

{ cout<<\};

void main (void)

{ Strucf s1(2,3),s2(2.75),s3; s1.Print(); s2.Print(); s3.Print(); } 解:

call Strucf(int, int) call Strucf(float) a=3 b=3 max=3

a=2.75 b=10 max=10 a=0 b=0 max=0

call ~Strucf() call ~Strucf() call ~Strucf()

例17、 阅读下列程序,说出初始化对象成员c1过程,并写出程序运行结果。 # include class A

{ private: int X,Y; public:

A(int a,int b) { X=a;Y=b;} void Show( )

{ cout<<\ };

class B { private:

int Length,Width; public:

B(int a,int b)

{ Length=a; Width=b;} void Show( )

{ cout<<\};

class c

{ private: int R,High; A a1; B b1; public:

c(int a,int b,int c,int d,int e,int f):a1(e,f),b1(c,d) { R=a;High=b;} void Show( )

{ cout<<\ a1.Show( );

b1.Show( ); } };

void main(void)

{ c c1(25,35,45,55,65,100); c1.Show( ); } 解:

R=25 High=35 X=65 Y=100 Legnth=45 Width=55

友元、运算符重载与类中使用数组

1、 友元

类具有数据封装和隐藏的特性,只有类的成员函数才能访问类的私有成员,友元可以在类外部直接访问类的私有成员 (1) 友元函数

友元函数不是当前类的成员函数,而是独立于当前类的外部函数

友元函数要在类定义时声明,声明时要在其函数名前加上关键字friend 注意:除了可以将普通函数声明为类的友元函数外,也可以将另外一个类的成员函数声明为一个类的友元函数 (2) 友元类

一个类作为另一个类的友元称为友元类,友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息。

友元类可以在另一个类的公有部分或私有部分进行说明,说明方法如下: friend <类名>; //友元类类名 2、 运算符重载

运算符重载是函数重载的一种特殊情况,关键字operator和c++的一个运算符连用,构成一个运算符重载函数名 注意: ① 运算符重载是针对c++中原有运算符进行的 ② 运算符重载函数作为成员函数时,第一操作数就是对象本身,并不显式出现在参数

列表中

运算符重载的形式:

① 将运算符重载函数说明为类的成员函数(成员函数中有隐含参数的this指针) ② 将运算符重载函数说明为类的友元函数(友元函数没有隐含参数的this指针)

3、 类与数组 (1) 对象数组

对象数组是指数组元素为对象的数组,该数组中的每一个元素都是同一个类的对象。定义格式: <类名> <数组名>[<大小>]; (2) 数组作为类的成员

(3) 动态数组作为类的成员

注意:在构造函数中为动态数组分配空间,在析构函数中释放空间

1、友元函数

独立于任何类的普通的外界函数,被允许访问该类的所有对象的私有成员。声明时在其函数名前加上关键字friend.

例:定义友元函数,访问myclass类的私有变量。

注:友元函数不是该类的成员函数,注意定义的格式和访问的格式。

2、友元成员

一个类的成员函数是另一个类的友元。声明时在需要加上成员函数所在的类名和friend。 例如: class A {

Public:

Friend B::f(); }

class B {

Public: F(); }

3、友元类

一个类是另一个类的友元。这个类的所有成员函数都可以访问另一个类的私有成员。声明时为friend class classname; 不具有传递性和交换性 例: class B {

..... //B类的成员声明 friend class A; //声明A为B的友元类 ..... }; 例: class A {

public:

void Display() { cout << x << endl; } int Getx() { return x; }

friend class B; //B类是A类的友元类 //其他成员略 private:

int x; }

class B

{

public:

void Set ( int i ); void Display (); private: A a; };

void B::Set ( int i ) {

a.x = i; // 由于B是A的友元,所以在B的成员函数中可以访问A类对像的私有成员 }

8.2 overloaded operators

重载体现了面向对象程序设计的多态性。 1、构造函数重载 例:class timer{ Int seconds; Public: Timer();

Timer(char *t) {seconds=atoi(t) ; };

Timer(int t); Timer(int,int); Int gettimer() ; }

Main()

{timer t1,t2(―123―),t3(23),t4(2,23) ; ... }

2、类成员函数重载 例:class date{ int year ; int month; int day; public : data() ;

date(int,int,int) ; date(char *) ;

void getdate(char *) ;

void getdate(int *, int*,int*) ;

}

3、普通函数的重载

4、运算符重载

重载运算符增强了c++语言的可扩充性。系统提供了许多预定义的运算符,例如整型变量x+y。而对象class x{ Int y ; ... }

Main() {

X a,b ;

a.y=a.y+b.y ;

} //分析可能存在的问题:不简洁;私有成员的访问 如何实现a=a+b,重新解释+的含义:运算符重载。

注:大部分运算符可以重载,但是下列运算符不允许: . :: * ?: #

(1)用成员函数重载运算符 ? 在类定义中声明运算符函数

type operator @(参数表) ;

注:若运算符是单目运算符,这参数表为空,此时当前对象为运算符的单操作数;若运算符为双目,则参数表中有一个操作数,此时当前对象为此运算符的左操作数,参数表中的操作数为有操作数。 ? 定义运算符函数

type x ::operator @(参数表) {

//定义操作 }

例:class point{ Int x,y ; Public :

Point(int vx, int vy) {x=vx ;y=vy ;} Point()

{x=0 ;y=0 ;}

Point operator + (point p1) ; Point operator - (point p1) ; Void print(); }

Main() {

Point p1(10,10),p2(20,20)

P1=p1+p2 ; P1.print() ; Return 1 ; }

//分析this和没有返回值的情况下,运算符的操作数顺序的影响! 练习:class three_d{

three_d operator = (three_d t) ; }

(2)用友元重载运算符 ? 在类中声明重载运算符

friend type operator @(参数表) ; 注:不是类的成员函数,没有this指针。若是单目运算符,则在参数表中有一个操作数;若是双目运算符,则在参数表中有两个操作数。也就是所有操作数均需通过参数来传递。 ? 定义重载运算符

type operator @(参数表) {

//定义操作 }

point operator + (point p1, point p2) {

Point p;

p.x=p1.x+p2.x; p.y=p1.y+p2.y; return p ; }

例:用重载运算符的方法进行复数运算

(5)++和—的重载 class counter{ Unsigned int value; Public:

counter() value=0{}; Void operator ++(); Void operator –(); Int getvalue() {return value;} };

练习:增加point 类的++ --

(6)【】和()的重载

注:只能用成员函数的方式重载,不能使用友元的方式 例:定义一个实数矩阵,定义加减乘和()运算符

(7)赋值运算符的重载

注:只能重载为成员函数,并且不能被继承。一般情况下,系统为每一个类产生一个默认的赋值运算符,但在有些情况下会出现问题。例如: class string{ char *contents; Int size; Public:

String (int sz){

contents=new char[sz];} ~string (){delete contents;} };

Main() {

String s1(10); String s2(20); S1=s2; Return 1;

} //分析存在的问题 Void string::operator =(string *str)

8.3 Type conversion

1、一般数据类型转换 (1)标准类型转换 (2)显式类型转换 ? 强制类型转换

(类型名)表达式 ? 函数法

类型名(表达式)

2、通过构造函数进行类型转换 例:#include using namespace std;

class example{ public:

example (int);

example (const char *,int =0); };

int main()

{

example a=example(1); example b=3;

example c=\ return 0; }

3、类类型转换函数 成员函数:

(1)声明operator type(); 此函数没有参数也没有返回值 (2)函数的定义 例:举例演示

destructor 、copy constructor 、copy assignment operator

My_Array first; // initialization by default constructor

My_Array second(first); // initialization by copy constructor

My_Array third = first; // Also initialization by copy constructor

second = third; // assignment by copy assignment operator

#include %using namespace std; class Rec

{int length,width; public:

Rec (int len=10,int wid=10) {length=len;width=wid;} Rec (const Rec& p)

{length=3*p.length;width=3*p.width;} void disp()

{cout<<\};

int main()

{Rec p1(20,30); p1.disp(); Rec p2;

p2=p1; p2.disp(); Rec p3=p2; p3.disp(); return 0; }

分别编译与命名空间

1、 分别编译

了解分别编译的含义和使用

条件指示符#ifndef 的最主要目的是防止头文件的重复包含和编译,注意#ifndef的使用 2、 命名空间

在c++中,名称(name)可以是符号常量、变量、宏、函数、结构、枚举、类和对象等等。为了避免,在大规模程序的设计中,以及在程序员使用各种各样的c++库时,这些标识符的命名发生冲突,标准c++引入了关键字namespace(命名空间/名字空间/名称空间/名域),可以更好地控制标识符的作用域。

(1) 命名空间的定义

(2) 命名空间内变量和函数的方法的引用方法:

① 使用作用域运算符:: 命名空间名::成员名 ② 使用using指令 using namespace 命名空间名 ③ 使用using声明 using命名空间名::成员名

继承

继承是面向对象程序设计的一个重要特性,是软件复用的一种形式,它允许在原有类的基础上创建新的类 1、 继承与派生 (1) 基本概念

类的继承就是新的类从已有的类中得到已有的特性。从已有的类产生新类的过程就是派生。 从派生类的角度,根据它所拥有的基类数目不同,可以分为单继承和多继承 (2) 派生类的定义与构成

派生类定义的一般格式如下:

class <派生类名>:<继承方式1> <基类名1>,<继承方式2> <基类名2>,??, <继承方式n> <基类名n> { <派生类新定义成员> };

继承方式包括公有继承(public)、私有继承(private)和保护继承(protected)3种访问属性。

从基类继承下来的全部成员构成派生类的基类部分,这部分的私有成员是派生类不能直接访问的,公有成员和保护成员则是派生类可以直接访问的,但是它们在派生类中的访问属性将随着派生类对基类的继承方式而改变 2、 派生类对基类成员的访问

派生类中的成员不能访问基类中的私有成员,可以访问基类中的公有成员和保护成员。 派生类从基类公有继承时,基类的公有成员和保护成员在派生类中仍然是公有成员和保护成员;派生类从基类私有继承时,基类的公有成员和保护成员在派生类中都改变为私有成员;派生类从基类保护继承时,基类的公有成员在派生类中改变为保护成员,基类的保护成员在派生类中仍为保护成员。

注意:可以将基类的私有成员中需要提供给派生类访问的成员定义为保护成员

3、 派生类的构造函数和析构函数 (1) 派生类的构造函数

一般格式为:

<派生类名>::<派生类名> (<总参数表>):<基类名1> (<参数表1>),?, <基类名n> (<参数表n>),<成员对象名1> (<参数表n+1>),?, <成员对象名m> (<参数表n+m>) { <派生类构造函数体> };

建立派生类对象时,构造函数的执行顺序如下:

先按照各个基类被继承时声明的顺序执行基类的构造函数,再按照各个成员对象在类中声明的顺序执行成员对象的构造函数,最后执行派生类的构造函数 (2) 派生类的析构函数

派生类的析构函数的定义与基类无关,它只负责对新增普通成员的清理工作 派生类的析构函数的执行顺序与构造函数的执行顺序相反 4、 虚基类的定义

当一个派生类从多个基类派生,而这些基类又有一个共同的基类,当对该基类中说明的成员进行访问时,可能出现二义性,虚基类就是为了解决这种二义性问题提出的 虚基类的说明格式:class <类名>:virtual <继承方式> <基类名> 5、 虚基类的构造函数

只有用于建立对象的最派生类的构造函数才调用虚基类的构造函数,此时最派生类的所有基类中列出的对虚基类的构造函数的调用在执行过程中都被忽略,从而保证对虚基类子对象只初始化一次

虚基类的构造函数先于非虚基类的构造函数执行 6、 多态性的概念

一个面向对象的系统常常要求一组具有相同基本语义的方法能在同一接口下为不同的对象服务,这就是所谓多态性

多态性可分为:编译时的多态性(通过函数重载和模板体现的)和运行时的多态性(通过虚函数体现的) 7、 虚函数

声明的时候加上virtual修饰,虚函数可以是成员函数、友元函数,但不是静态成员函数。 虚函数在派生类被重定义后,重定义的函数仍然是一个虚函数 8、 虚析构函数

析构函数可以通过virtual声明的

通常,只要派生类中包含有虚函数的重定义,而且对析构函数进行了专门声明,其基类的析构函数就应当声明为虚函数 9、 纯虚函数与抽象类

纯虚函数是在虚函数原形的语句结束符;之前加上=0。拥有纯虚函数的类称为抽象类,抽象类不能用来定义对象。

在c++语言中,通过继承,可以让一个类拥有另一个类的全部属性,也即让一个类继承另一个类的全部属性。

可以把继承过程看成是从一个类派生出一个新类的过程。派生出来的新类称为派生类或子类;而被继承的类称作基类或父类。

一个基类可以派生出多个派生类,一个派生类也可以由多个基类派生而来。称从一个基类派生出一个派生类的过程为单继承;从多个基类派生出一个派生类的过程为多继承。

10.1 单继承

通过单继承派生一个类的一般形式如下: class 派生类名:派生类型 基类名 { 派生类新成员声明 };

注:1、指明了这个派生类的基类,这个基类必须在声明这个派生类之前已经声明,否则,会导致编译错误。

2、用关键字public 、private或 protected指定,如果省略,系统将默认为私有派生。 (1) 共有派生 public

当采用公有派生时,基类中的公有(public)成员和保护(protected)成员的访问权限在派生类中保持不变,而基类的私有(private)成员无论是在派生类中,还是在类外都是不可访问的。

注:在公有派生时,基类和派生类中的公有成员都可以直接通过派生类的对象名来访问。一个类的保护成员只能被它自己的成员函数或它的派生类的成员函数访问,在类外不能直接访问。

(2) 私有派生 private

当采用私有派生时,基类的私有(private)成员与公有派生时相同,无论是在派生类中,还是类外都是不可访问的。但基类中的公有(public)成员和保护(protected)成员的访问权限在派生类中则变为私有。

(3) 保护派生 protected

当采用保护派生时,基类的私有(private)成员与公有派生时相同,无论是在派生类中,还是类外都是不可访问的。但基类中的公有(public)成员和保护(protected)成员的访问权限在派生类中则变为保护。

注:引入保护成员的意义在于使类中的这些成员对该类及它的派生类是可见的,但在程序的其他部分与私有成员一样,是不可见的。

在程序的任何部分如果声明有一个类的对象,就可以通过这个对象访问对象所属类中的所有公有成员,但不能访问其私有和保护成员;而一个派生类的函数成员则可以访问所属类中的新声明的所有成员,以及派生它的基类中的公有和保护成员。一个类的私有成员只能被所属类的成员函数访问,在其他任何地方都不可见。 10.2 多继承 声明形式如下: class 派生类名:派生类型1 基类名1,派生类型2 基类名2,…派生类型n 基类名n { 派生类新成员声明 };

注:每一个派生类型对应的是紧接其后给出的基类,而且必须给每个基类指定一种派生类

型,如果缺省,相应的基类则取私有派生类型,而不是和前一个基类取相同的派生类型。

10.3派生类的构造函数与析构函数

在创建派生类的对象时,系统将首先调用其基类的构造函数来初始化从基类中继承的数据成员,然后调用派生类自身的构造函数初始化在派生类中新声明的数据成员。终止对象时,析构函数的执行顺序则正好相反,即先调用派生类的析构函数清除派生类中新声明的数据成员,再调用基类的析构函数清除从基类中继承的数据成员。

调用带有初始化列表的构造函数 格式: 派生类名::派生类名(参数总表):基类1(参数表1),基类2(参数表2),…,基类n(参数表n),对象成员1(对象成员参数表1),对象成员2(对象成员参数表2),…,对象成员n(对象成员参数表n) { 派生类中新声明的数据成员初始化语句 }

注:1、参数总表应包括初始化基类数据成员、内嵌对象数据成员及其他数据成员所需的全部数据。

2、是需要用参数初始化的基类名、对象成员名及各自对应的参数表,基类名和对象成员名之间的顺序可以是任意的,且对于使用默认构造函数的基类和对象成员,可以不列出基类名和对象成员名。对象成员是指在派生类中新声明的数据成员,它是属于另外一个类的对象。

3、派生类构造函数的一般执行顺序

(1) 调用基类的构造函数,调用顺序与声明派生类时基类在声明语句中出现的顺序一致。 (2) 调用对象成员的构造函数,调用顺序是按照它们在派生类声明中的顺序。 (3) 调用派生类的构造函数。 4、派生类析构函数的一般执行顺序 (1) 调用派生类析构函数。

(2) 调用派生类中新增对象成员的析构函数,顺序与它们在派生类中声明 的顺序相反。

(3) 调用基类的析构函数,调用顺序与声明派生类时基类在声明语句中出 现的顺序相反。

10.4 二义性问题

公有成员:是由派生类新声明的或基类的公有成员经公有派生而来的。

派生类实际包含的成员

保护成员:可以是由派生类新声明的成员、基类的保护成员公有派生的或基类的公有成员和保护成员经保护派生而来的。

私有成员:是由派生类新声明的或基类的公有和保护成员经私有派生而来的。

类中不可直接访问的成员:是由基类的私有成员派生而来的。

(1)派生类中新声明的成员与基类成员重名

对于这种派生类中的成员函数与其基类成员重名的现象,派生类中的成员函数将覆盖所有基类中的同名成员。这一规则对于数据成员也同样适用。

(2)如果只是基类与基类之间的成员重名,在这种情况下,系统将无法自行决定调用的是哪一个函数。

注:1、对于这种二义性问题,通过在程序中指定想要访问的成员来消除二义性,这时就需要使用作用域运算符“::”。

2、由于存在二义性,所以一个类不能直接从同一类继承一次以上。

3、一个派生类由多个基类派生而来,而这多个基类中的部分或全部又有一个公共的基类,则这些具有公共基类的多个直接基类,从上一级公共基类中继承而来的成员就有相同的名称。

在派生类中访问这些公共基类成员时,就存在二义性问题。

10.5 虚基类

c++语言允许程序中只建立公共基类的一个副本。这时,就需要在声明直接派生类时,将这个公共基类声明为虚基类。

在声明派生类时,虚基类的声明形式如下: class 派生类名:virtual 派生类型 基类名

注:c++语言规定,对于继承过程中的虚基类,它们由最后派生出来的用于声明对象的类来初始化。而这个派生类的基类中对这个虚基类的初始化都被忽略。虚基类的构造函数也就只被调用一次。

例:说明虚基类的初始化和构造函数的调用方法。

10.6 赋值兼容原则

赋值兼容规则是指在公有派生条件下,任何可以使用基类对象的地方都可以用其派生类的对象替代。

(1)可以用派生类的对象给基类对象赋值; 比如: class A{...}; class B:public A{void fun() {...}};

A a;B b; a =b; //将对象b中所含类A成员的值赋给对象a

(2)可以用派生类的对象初始化基类对象的引用; 比如: B b; //类A、类B的意义同上 A &a=b; //用派生类对象初始化基类对象的引用 (3)可以用派生类对象的地址给基类对象的指针赋值。 比如: B b; //类A、类B的意义同上 A *pa =&b; //用派生类对象的地址初始化基类对象的指针

注:第2、3种方法建立的基类对象的引用和指针,只能用来访问派生类对象中从基类继承下来的成员。如果需要用它们来访问派生类中新增的成员,就必须对它们进行强制类型转换。比如,利用指针pa访问类B对象中的一个成员函数fun(),就需要采用下述语句: (B*)pa->fun();

10.7 多态性

多态就是指不同的对象接受到相同的消息时产生不同的响应动作,即对应相同的函数名,却执行了不同的函数体(当然,这些函数体还是要事先定义好,以便调用)。

这种把程序标示符与和一个存储地址相联系的过程,称为联编(binding,又译为绑定)。

通过基类指针访问虚函数

举例:

声明了基类指针,就可以使不同的派生类对象产生不同的函数调用,实现了程序的运行时多态。运行时多态应该使用虚函数,并通过指针、引用或者成员函数调用虚函数。

声明虚成员函数的语法: virtual 函数类型 函数名称(形式参数表);

虚函数的声明只能出现在类声明时的函数原型声明中。在派生类中可以不显示地声明为虚函数,系统会自动判别该函数是虚函数或者重载函数。

抽象类

函数无法具体实现(或不必实现)。这样的函数可以声明为纯虚函数。对于纯虚函数,可以不必定义它的函数体。含有纯虚函数的类被称为抽象类。

设计抽象类的目的就是为了多态地使用它的成员函数,由此为整个类族规定统一的接口形式。

纯虚函数的语法形式是: virtual 函数类型 函数名(参数表)=0; 特点是在函数名及参数表后面多了一个“=0”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值