自学考试【04737】C++程序设计这门课程,主要分为两个阶段,一个是基础阶段,一个是进阶阶段。
第1~5章为基础阶段,主要讲述C++程序设计基础,与C语言的区别,完全看完后,能阅读简单的C++程序编程。
第6~9章为进阶阶段,主要对C++程序设计进行深入学习,学习掌握后,能做到阅读大部分C++程序编程。
仅仅是想考过自学考试,在记下本文知识考点,辅以课本课后习题+往年真题,基本能考过本科目。
若想进一步从事程序编程行业,仅仅通过做题还是不够的,需要大量阅读学习前辈编程实例,多找些C++应用编程实例进行练习。
(2020/10/21更新)
C语言的结构变量,在C++中则称为结构对象
1.混合型语言
C++程序以.cpp作为文件扩展名,有且只有一个主函数main()。C++保留了这个面向过程的主函数,所以称之为混合型语言。
2.注释方式
/* 多行注释 */ 和 // 单行注释
3.输入和输出
Iostream.h 是I/O流的标准头文件。
C++预定义了4个流:cin、cout、cerr和clog。这4个流与4个具体的输入输出设备相联接,具体的联接设备如下:
(1)cin与标准输入设备相联接;
(2)cout与标准输出设备相联接;
(3)cerr与标准错误输出设备相联接(非缓冲);
(4)clog与标准错误输出设备相联接(缓冲)。
在默认情况下,操作系统指定标准输出设备式显示终端,标准输入设备式键盘。而标准错误输出设备则总是显示终端。当然,可以将标准输入输出设备重定向为其他设备,比如文件等。
4.使用命名空间
using namespace std; //使用命名空间
5.对象的定义和初始化
Int z(0); 等价于int z=0;
6.函数原型及其返回值
函数有库函数和自定义函数。使用变量规则:必须先声明,后使用,函数也是。
7.const修饰符
使用const修饰的标识符定义是一类特殊的常量,称为符号常量,或const变量。
1.函数重载
同一函数定义几个版本,从而使一个函数名具有多种功能。
2.数据类型
(1)void,C++增加的数据类型,无类型标识符,只能声明函数的返回值类型,不能声明变量。
(2)整数常量有4种类型:十进制常量、长整形常量、八进制常量和十六进制常量。
3.动态分配内存
new 类型名[size]
指针名 = new 结构名;//分配
delete 指针名; //释放
4.引用
引用实际就是变量的别名。
数据类型&别名=对象名;
如: int x = 9; int &a = x;
5.对指针使用const限定符
指向常量的指针:const int *p;
常量指针:int * const p = &x;
指向常量的常量指针:const int * const p = &x;
6.数据的简单输入与输出
两种格式控制方式:iso_base 类提供的接口;操控符的特殊函数。
表1-1 操控符及其含义
名称 | 含义 | 作用 |
---|---|---|
dec | 设置转换基数为十进制 | 输入/输出 |
oct | 设置转换基数为八进制 | 输入/输出 |
hex | 设置转换基数为十六进制 | 输入/输出 |
endl | 输出一个换行符并刷新流 | 输入/输出 |
resetiosflags(long flag) | 清楚flag指定的标志位 | 输出 |
setiosflags(long flag) | 设置flag指定的标志位 | 输出 |
setfill(char ch) | 设置ch为填充字符 | 输出 |
setprecision(int n) | 设置浮点数输出精度n | 输出 |
setw(int width) | 设置输出数据字段宽度width | 输出 |
表1-2 常量及其含义
常量名 | 含义 |
---|---|
ios_base::left | 输出数据按输出域左边对齐输出 |
ios_base::right | 输出数据按输出域右边对齐输出 |
ios_base::showpoint | 浮点输出时必须带有一个小数点 |
ios_base::showpos | 在正数前添加一个“+”号 |
ios_base::scientific | 使用科学计数法表示浮点数 |
ios_base::fixed | 使用定点形式表示浮点数 |
源文件中含有包含头文件的预编译语句,经过预编译后,产生翻译单元,以临时文件的形式存放于计算机之中。之后进行编译,进行语法检查,产生目标文件(*.obj)。此工程的所有目标文件经过连接,产生可执行文件。连接包含C++的库函数和标准类库的连接。
(2020/10/21更新)
1.结构发生质的演变
(1)函数于数据共存
struct 结构名
{数据成员
成员函数
};
访问成员方法:结构对象.成员函数
默认访问权限是public类型
(2)封装性
将数据成员使用private关键字定义,则产生封装性。
2.使用构造函数初始化的对象
3.构造函数名对象名(初始化参数)
关键字class代替struct,就是一个标准的类。
面向过程,不必了解计算机内部逻辑,精力集中在算法逻辑和过程。
面向对象设计,进行功能和数据抽象,对象是功能和数据抽象的统一。
1.对象
C++使用对象名、属性和操作三要素来描述对象。
2.抽象和类
类是具有相同的属性和操作的一组对象集合,其内部包含属性和操作两个主要部分。类的作用是定义对象。
类给出了属于该类的全部对象的抽象定义,而对象则是符合这种定义的实体。所以将对象称作类的一个实例。
3.封装
将类封装起来,也是为了保护类的安全。所谓安全,就是限制使用类的属性和操作。
在类中,封装是通过存取实现的,对象的外部只能访问对象的公有部分,不能直接访问对象的私有部分。
4.继承
继承是一个类可以获得另一个类的特性的机制,继承支持层次概念。
5.多态性
不同的对象可以调用相同名称的函数,但可导致完全不同的行为的现象称为多态性。
1.使用string对象,必须包含这个类的头文件string.h,即#include
对象使用自己成员函数的方法是:
对象名.成员函数
常用的函数如下:
size():计算并输出字符串长度。
substr():用来返回字符串的子串。
find():用来在主串中检索所需字符串,找到返回所在位置,找不到返回-1。
2.使用complex类定义复数对象,需包含头文件:
#include
complex <数据类型>对象名(实部值,虚部值);
complex 的real 和image成员函数用来输出对象的实部和虚部的值。
string 类有一对用来指示其元素位置的基本成员函数:指示第一个元素的begin和指示最后一个元素之后的字符串结束标记end。
(2020/10/22更新)
函数参数有两种传递方式:传值和传引用。传引用其实就是传对象的地址,所以也称传地址方式。
1.对象作为函数参数
将对象作为函数参数,是将实参对象的值传递给形参对象,这种传递是单向的。形参拥有实参的备份,当在函数中改变形参是,改变的是这个备份中的值,不会影响原来实参的值。
2.对象指针作为函数参数
将指向对象的指针作为函数参数,形参是对象指针(指针可以指向对象的指针)实参是对象的地址值。虽然参数传递方式仍然是传值方式,但因为形参传递的就是实参本身,所以当在函数中改变形参的值时,改变的就是原来实参的值。
#include
#include
void swap(int *a , int *b ); //函数原型声明
void main()
{
int x=3;
int y=4;
cout<<"Before swap"<<"x="<
运行结果:
Before swap x=3 y=4
After swap x=4 y=3
3.引用作为函数参数
形参是对象的引用,实参是对象,实参和形参对象代表一个对象,所以改变形参对象的值就是改变实参的值。
4.默认参数
默认参数是在函数原型中说明的,默认参数可以多于1个,但必须放在参数序列的后部。
如:Display(string s1 , string s2=” ”,string s3=” ” );
5.使用const保护数据
用const修饰传递参数,它只能使用参数而无权修改它。这主要是为了提高系统的自身安全。
返回值类型可以是除数组和函数以外的任何类型。当函数返回值是指针或引用对象时,需要注意的是,函数返回所指的对象必须继续存在,因此不能将函数内部的局部对象作为函数的返回值。
1.返回引用的函数:数据类型 & 函数名(参数列表);
例如声明:
int & index (int i);
调用:
index(3);
2.返回指针的函数:类型标识符&函数名(参数列表);
例如声明:
float *a=input(num);
调用:
float *a= input(num);
3.返回对象的函数:
例如声明:
string input(const in);
调用:
string str = input(n);
4.函数返回值作为函数的参数:
例如声明:
int max(int,int);
调用:
cout<
定义内联函数使用关键字inline。在C++中,除具有循环语句、switch语句的函数不能说明为内联函数外,其他函数都可以说明为内联函数。
使用内联函数能加快程序执行速度,但如果函数体语句多,则会增加程序代码的大小。由于编译器必须知道内联函数的函数体,才能进行内联替换,因此,内联函数必须在程序中第一次调用此函数的语句出现之前定义。
不同对象调用函数名具有多种功能,称这种特征为多态性。分为静态多态性和动态多态性。
函数重载是静态多态性。
double max(double m1 , double m2 ){...}
int max(int m1, int m2){...}
char max(char m1, char m2){...}
带默认值参数:
int add (int m1 =0, int m2 = 0, int m3 =0 , int m4 =0){...}
将函数模板与某个具体数据类型连用,就产生了模板函数。
函数模板定义:
函数模板名:<模板参数>(参数列表)
默认形式:函数模板名(参数列表)
C++还专门定义一个仅仅用在模板中的关键字typename,它的用途之一是代替templata参数列表中的关键字class。
例如:
#include
using namespace std;
template
T max(T m1 , T m2)
{
return (m1>m2)?m1:m2;
}
template
T min(T m1 , T m2)
{
return(m1(8 , 3)<
运行结果:
aBC
2.3
3
(2020/10/23更新)
对象就是一类物体的实例,将一组对象的共同特征抽象出来,从而形成“类”的概念。
1.定义类
类中声明的任何成员不能使用extern、auto和register关键字进行修饰,类中有数据成员和函数成员,而且不能在类声明中对数据成员使用表达式进行初始化。
(1)声明类
class 类名
{
private:
私有数据和函数
public:
公有数据和函数
protected:
保护数据和函数
};
如果没有使用关键字,则所有成员默认声明为private权限。public、private和protected的使用顺序和次数也都是任意的。
(2)定义成员函数
类中声明的成员函数用来对数据成员进行操作,还必须在程序中实现这些成员函数。
返回类型类名::成员函数名(参数列表)
{成员函数的函数体}
“::”是作用域运算符,“类名::成员函数名”的意思就是对属于“类名”的成员函数进行定义,而“返回类型”则是这个成员函数返回值类型。
可以使用inline将成员函数定义为内联函数。
如果在声明类的同事,在类体内给出成员函数的定义,则默认为内联函数。
(3)数据成员的赋值
不能在类体内给数据成员赋值。数据成员的具体值是用来描述对象的属性的。只有产生了一个具体的对象,这些值才有意义。如果在产生对象时就使对象的数据成员具有指定值,则称为对象的初始化。
2.类的对象
对象和引用都使用运算符“.”访问对象的成员,指针则使用“- >”运算符。
成员使用规律:
(1)类的成员函数可以直接使用自己类的私有、保护和公有成员(数据成员和成员函数)。
(2)在类外不能直接使用私有、保护成员,但可以使用公有成员来间接访问私有、保护成员。
(3)可以定义类的多个对象,不同的对象可以有不同的属性值。
定义类对象指针的语法:
类名 * 对象指针名;
对象指针名 = 对象的地址;
Point a ; Point * p2 ; p2=&a;
也可以直接进行初始化:
类名 * 对象指针名 = 对象的地址;
如:
Point a ; Point * p2 = &a ;
类对象的指针可以使用“- >”运算符访问对象的成员:
对象指针名 - > 对象成员名
如:
p2 - > x ; p2 - > fun( );
3.数据封装
C++对其对象的数据成员和成员函数的访问是通过访问控制权限来限制的。C++通过类实现数据封装,即指定类成员的访问权限来实现。
构造函数对象用来设置初值的函数。
构造函数的特征:
1.构造函数名称与类名相同且可以由用户自行定义。
2.构造函数不能有返回值,也不能声明返回类型。
错误的构造函数声明:
void Grade( char * stu_name , int stu_grade );
char * Grade( char * stu_name , int stu_grade );
3.构造函数可以有多个,即构造函数可以重载。
4.构造函数在建立对象时自动被调用。
5.没有参数的构造函数称为默认构造函数(default constructor)。没有定义构造函数时,自动产生默认构造函数。
6.构造函数也受数据封装限制而有数据隐藏的特性。
构造函数设置成员初值方法有两种:一种是在函数体内赋值,另一种是采用初始化列表的形式。
类名::类名(形参1,形参2,...,形参n)
{数据成员1 = 形参1;
数据成员2 = 形参2;
...
数据成员n =形参n;
}
类名::类名(形参1,形参2,...,形参n):数据成员1(形参1),数据成员2(形参2),...,数据成员n(形参n)
{
...
}
这两种形式等价,但有些不能在函数体内赋值,如对于常数类型(const)与引用类型(reference),必须在定义时给定数值。但类中不可以做处置设置,这时就必须使用到成员初始化列表形式。
7.构造函数和运算符new : new用于建立生存期可控的对象,new返回这个对象的指针。
使用new建立的对象,只能用delete删除,以便释放所占用空间。
如:
Point * ptr1 = new Point;
delete ptr1;
8.带默认值的构造函数:即在声明中指定形参的默认值。
9.复制构造函数:复制构造函数用已有的对象来建立新对象,必须使用对象的引用作为形参
复制构造函数将真正产生两个各自战友独立空间且有相同内容的对象。复制构造函数的定义:
X :: X(X&)
{
...
}
为了防止修改原有对象,形参使用常引用即X :: X(const X &)。
对象删除时被调用的函数
1.析构函数与类的名称一样,但在名称的前面加上波浪号(~)。
(1)析构函数不能有返回值,也不能声明返回类型。
(2)析构函数不能有任何参数。
(3)允许有一个唯一的析构函数,即不能重载。
析构函数作用:释放新定义对象时用户通过构造函数向系统申请的存储空间。对象离开其有效范围时,便会自动调用析构函数。
全局对象和静态对象的析构函数在程序运行结束之前调用。类的对象数组的每个元素调用一次析构函数。全局对象数组的析构函数在程序运行结束之前被调用。
2.析构函数和运算符delete
运算符delete与析构函数一起工作。当使用delete删除一个动态对象时,它首先为这个对象调用析构函数,然后再释放这个动态对象占用的内存,这和使用new建立动态对象的过程正好相反。
3.默认析构函数:在定义类时没有定义析构函数,系统自动产生一个函数体为空的默认析构函数。
4.5成员函数重载及默认参数
函数可以重载,成员也可以重载,也可以带默认值。
1.this指针隐含于每一个类的成员函数中。每一个类的成员函数都有一个this指针变量,当某个对象调用成员函数时,this指针指向这个对象。
2.this指针由编译器自动产生、调用。通过this指针可以引用到对象的任何成员(静态成员没有this指针)。成员函数也可以显式使用this指针。经常省略“- >”。
例如:
void Point :: Setxy( int a, int b )
{
x = a; //等价于this- > x = a;
y = b; //等价于this- >y = b;
}
类的数据成员,可以是普通的数据类型,也可以是类对象类型。执行构造函数时,先执行成员对象的构造函数,再执行本身类的构造函数。
如:
#include
class A{
public : A( ){cout << “A” ;}
};
class B{
A , a;
public : B( ){cout << “B” ;}
};
void main(){
B b;
}
结果为:A B
1.对象的性质
(1)同类对象之间可以相互赋值。
(2)可使用对象数组。
(3)可使用指向对象的指针。
(4)对象可以用作函数参数。
(5)对象作为函数参数时,可使用对象、对象引用和对象指针。
(6)一个对象可以作为另一个类的成员。
2.类的性质
(1)使用类权限,类本身成员可以使用类的所有成员;类对象只能访问公有成员;其他函数不能使用类的私有成员,也不能使用公有成员函数。
(2)不完全的类声明,用于在类没有定义之前就引用该类的情况。不完全声明的类不能实例化。
如
class A;
class B{
A a;
...
};
class A{
};
(3)空类不包括任何声明,如class A{};
(4)类作用域:声明类时使用的一对花括号形成类作用域。
使用struct、class都可以设计类,但struct默认权限是public类型,而class默认为private类型。
面向对象解决代码编写OOP向面向对象分析OOA和面向对象设计OOD发展。人们采用图像形式将面向对象分析、设计何事项阶段对问题的描述直观地表示出来。1992年OMG(面向对象管理组)制定的面向对象分析和设计的国际标准UML问世。
UML是一种可视化建模语言,主要用于面向对象分析和建模。
1.类和对象的UML标记图
2.对象、类和消息
对象的属性是描述对象的数据成员。数据成员可以是系统或程序员定义的数据类型、对象的属性的集合又称为对象的状态。
对象的行为是定义在对象属性上的一组操作的组合。操作是响应消息而完成的算法,表示对象内部实现的细节。对象的操作集合体现了对象的行为能力。
对象的属性和行为是对象的组成要素,分别代表了对象的静态和动态特征。
消息是向对象发出的服务请求,它是面向对象系统中实现对象间的通信和请求任务的操作,消息传递是系统构成的基本元素,是程序运行的基本处理活动。
一个对象可以同时向多个对象发送消息,也可以接受多个对象发来的消息。消息值反映发送者的请求,由于消息的识别和解释取决于接受这,因而同样的消息在不同对象中可解释成不同的行为。
对象传送的消息一般有3部分组成:接受对象名、调用操作名和必要的参数。在C++中,一个对象的可能消息集是对象的类描述中说明的,每个消息在类描述中用一个相应的方法给出,即使用成员函数定义操作。
消息协议是一个对象对外提供服务的规定格式说明,外接对象能够并且只能向该对象发送协议中所提供的消息,请求该对象服务。协议是一个对象所能接受的所有公有消息的集合。
规模较大的类,一般包括一个头文件和一个实现文件。
所有编译指令都以#开始,每条指令单独占用一行。
(1)嵌入指令#include指标编译器将一个源文件嵌入到带有#include指令的源文件中该指令所在的位置处。尖括号或双引号中的文件名可含有路径信息。
(2)宏定义:#define 宏名 替换正文。#include指令定义一个标识符及串,在源程序中,每次遇到该标识符时,编译器均用定义的串代替之。该标识符称为宏名,而将替换过程称之为宏替换。
(3)条件编译指令是:#if、#else、#endif。
(4)关键字defined不是指令,而是一个预处理操作符。用于判定一个标识符是否已经被#define定义。已被定义为真,否则为假。
(2020/10/24更新)
class A{
类名1 成员1;
类名2 成员2;
…
类名n 成员n;
};
A类的构造函数的定义形式为:
A::A(参数表0):成员1(参数表1),成员2(参数表2),…,成员n(参数表n){}
对象成员构造函数的调用顺序取决于对象成员在类中说明的顺序,与它们在成员初始化列表中给出的顺序无关。
调用析构函数与调用构造函数顺序相反,先执行派生类析构函数,后执行对象成员的析构函数,最后才是基类的析构函数。
初始化const成员和引用成员时,必须通过成员初始化列表进行。
简单成员函数是指声明中不含const、volatile、static关键字的函数。如果类的数据成员或成员函数使用关键字static进行修饰,这样的成员称为静态数据成员或静态成员函数,统称为静态成员只能在类外进行初始化。在类外定义静态成员时,不使用static。
如:
class Test{
static int x;
};
int Test :: x=25。
静态数据成员,可以使用类名形式访问,即类名 :: 静态成员,或者使用对象访问,即对象 . 静态成员。
静态成员函数与一般成员的区别是:
(1)建立对象前,静态成员已经存在。
(2)静态成员时所有对象共享,被存储在公用内存中。
(3)静态成员函数不能直接访问非静态成员。
(4)静态成员函数不能被说明为虚函数。
(5)没有this指针
如:
#include
#invlude
class Count{
private:
static int count;
public:
Count(){count + + ;} ; //调用构造函数时,count 加1
static int get(){return count ; } ;
};
静态成员类在全局范围中赋初值:
int Count :: count = 0 ;
调用静态成员函数: Count :: get() ;
1.友元函数可以访问私有、公有和保护成员。友元可以是一个类或函数,尚未定义的类也可以作为友元引用。友元函数虽然在类的定义内声明,但仍不属于该类的成员。友元可以放在公用或私有部分。
使用友元的问题是:它允许访问对象的私有成员,破坏了封装和数据隐藏,导致程序可维护性变差,incident使用时要权衡得失。
友元函数可以在类中声明时定义。如果在类外定义,不能再使用friend关键字。
2.成员函数用做友元一定要指明友元函数所在的类。
如:
friend void One :: func(Two &);
友元关系不具有传递性,如类A是类B的友元,类B是类C的友元时,类A却不是类C的友元。
友元关系不具有交换性,如类A是类B的友元时,类B不一定是类A的友元。
常对象只能访问const成员函数,否则出错。
1.常量成员包括常数据成员、静态常数据成员和常引用。静态常数据成员需要在类外初始化。常数据成员和常引用只能通过初始化列表来获得初值。
常引用作为函数参数,传送的是地址。
void display(const double %r);
2.常对象声明的同时必须进行初始化。
语法:类名 const 对象名(参数表);
如:
Base const a(25 , 68);
3.常成员函数
类内声明:
类型标识符 函数名(参数列表)const;
类外:
类型标识符 类名 :: 函数名(参数列表)const{ 函数体 }
用内联函数定义:
类型标识符 函数名(参数列表)const{ 函数体 }
在常成员函数里,不能更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数。
用const声明static成员函数没有什么作用,在C++中声明构造函数和析构函数时使用const关键词均是非法的。
定义形式为:
类型(类名:: * 指正变量)(列表);
指向函数:
指针变量 = 类名 :: 函数名;
例如:
class A{
int X;
public : int value(int a ){}
};
void main(){
int(A :: * pfun)(int);
pfun = A :: value;
A obj(10);
(obj. * pfun)(15);
}
(2020/10/24更新)
用户利用继承从旧的数据类型定义出新的数据类型,在新的数据类型中不但拥有新的数据成员与成员函数,也可以同时拥有旧数据类型中的数据成员与成员函数。
被继承的类为基类,而通过继承关系而定义出的类成为派生类。
派生类可以增加或重新定义成员,同时派生类又可以产生新类,这就是类的层次性。
基类定义了对象的集合,派生类通过增添新的成员限制该定义,以便定义这个集合的子集。派生类使用两种基本的面向对象技术:
第一种为性质约束,即对基类的性质加以限制;
第二种称为性质扩展,即增加派生类的性质。
继承分为单一继承和多重继承。类的层次性,单继承便可形成倒挂的树。
1.一般形式
class 派生类名:访问控制 基类名{
private:
成员声明列表
protected:
成员声明列表
public:
成员声明列表
};
访问控制即访问权限有三种:公用public、私有private和保护protected。
2.派生类的构造函数和析构函数
派生类继承了基类的全部数据成员和除了构造、析构函数之外的全部成员函数。
派生类构造函数定义形式:
派生类名 :: 派生类名(参数表0): 基类名(参数表){}
在射击派生类的构造函数时,若在基类中定义了构造函数,派生类的构造函数必须调用基类的构造函数。即派生类的构造函数除了必须初始化自身的数据成员外,也必须初始化基类中的数据成员。
构造函数的调用顺序:
派生类与基类都定义有构造函数时,则编译器先调用基类的构造函数,如有对象成员则执行对象成员的构造函数,最后是派生类的构造函数。析构函数与构造函数顺序完全相反。
3.赋值兼容规则:指在公有派生情况下,一个派生类对象可以作为基类对象来使用的情况。
有三种情况:派生类对象可以赋给基类的对象,派生类的对象可以初始化基类引用,派生类对象的指针可以赋值给基类指针。
单一继承只从一个基类继承,而多重继承可以有多个基类。
其表示方式为:
class C : public A, public B{
...
}; //基类A与基类B同时为public基类
多重继承的派生类负责所有基类中的成员初始化。基类构造函数的调用顺序取决于定义派生类时指明的基类顺序。
多重继承中存在名字冲突解决不确定性,可以有两种选择:
(1)利用范围运算符指明引用的成员所属的类范围。类名 :: 标识符。
(2)重新定义基类中冲突的成员,隐藏基类中的同名成员。
派生类支配基类的同名函数。
派生类对象.成员函数
首先查找派生类的成员函数,如果有则执行派生类的成员函数,没有时执行基类的成员函数。如果要调用基类的成员函数则使用下面的形式:
派生类对象.基类 :: 成员函数;
(2020/10/26更新)
1.类模板的成分及语法
(1)函数模板,即建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。将函数模板与具体数据类型连用,就产生了模板函数,又称这个过程为函数模板实例化,这种形式就是类型参数化。
类模板与函数模板相似,将类中一些成员的类型,设计一个通用类型T,T是对类描述,称为类模板。类模板与某种特定数据类型联系起来,就产生一个特定的类称为模板类。类模板打打简化程序设计。
类模板声明的方法;
template<类模板参数>class类名{//类体};
(2)类模板的对象:类模板也称为参数化类。类模板定义对象格式如下:
类名<模板实例化参数类型>对象名(构造函数实参列表);
类名<模板实例化参数类型>对象名;;//默认或无参数构造函数
在类体外面定义成员函数时,必须用template重类型模板声明。格式化:
template<模板参数>
返回类型 类名<模板类型参数>::成员函数名(函数参数列表){//函数体}
例如:
#include
template
class A{
public:
T max(T a, T b);
};
template
T A::max(T a , T b){
return(a>b?) a : b;
}
void main(){
A at;
cout<
运行结果:34
2.类模板的派生与继承
类模板可以继承,继承的方式与普通的类一样。声明模板继承之前,必须重新声明类模板。
模板类的基类和派生类都可以是模板类。
1.定义向量列表
向量(vector)类模板定义在头文件vector中,它提供4种构造函数,用来定义由各元素组成的列表。length表示长度,type表示数据类型,name表示对象名。
vector
vector
vector
vector
不能使用列表初始化向量,但可以先初始化一个数组,然后吧数组的内容复制给向量。
如:
int iA[10] = {1,3,5,7,9,12,14,16,18,20};
vector Vb(iA , iA + 10);
iA是数组名,代表数组起始地址,iA +10是Vb的结束标志位,Vb的长度为10.因为向量自动产生一个结束标志,所以Vb不需要与iA等长,可以比iA长(值不确定)。
2.泛型指针
“与操作对象的数据类型相互独立”的算法称为泛型算法。
iterator在STL里面是一种通用指针,它在向量中的作用相当于T 。用iterator声明向量的正向泛型指针的一般形式;
vector::iterator 泛型指针名;
用iterator声明向量的逆向泛型指针的一般形式:
vector::reverse_itrator 泛型指针名;
例如:
vector::iterator p;//p指针,注意没有
3.向量最基本的操作方法
(1)size():返回当前向量中已经存放的对象的个数。
max_size():返回向量可容纳最多对象的个数。
capacity():返回无需再次分配内存就能容纳的对象个数。
empty() :当前向量为空时,返回true值。
(2)访问向量中对象的方法
front() :返回向量中的第一个对象。
back() :返回向量中的最后一个对象。
operator[] (size——type,n):返回向量中的第n+1个对象(下标为n的向量元素)
(3)在向量中插入对象的方法
push_back(const T&):向向量尾部插入一个对象
insert(inerator it,const T&):向it所指的向量位置前插入一个对象
insert(iterator it, size_typr n ,const T&X):向it所指向量位置前插入n个值为X的对象。
(4)在向量中删除对象的方法
pop_back(const T&):删除向量中最后一个对象。
erase(iterator it):删除it所指向的容器对象。
clear():删除向量中的所有对象,empty()返回true值
(2020/11/05更新)
静态联编所支持的多态性称为编译时的多态性,通过重载实现。动态联编所支持的多态性为运行时的多态性,由虚函数来支持。
1.静态联编中的赋值兼容性及名字支配规律
类的对象和调用的函数一一对应,编译时即可确定调用关系,从而产生编译时的多态性。
2.动态联编的多态性
当编译系统编译含有虚函数的类时,将为它建立一个虚函数表,表中的每一个元素都指向一个虚函数的地址。虚函数的地址取决于对象的内存地址,也就是不同对象具有不同的地址。
1.虚函数的定义
虚函数只能是类中的一个成员函数,但不能是静态成员,关键字virtual用于类中该函数的声明中。
virtual 返回类型 函数名(形参列表);
在派生类中定义了一个同名的成员函数时,只要该成员函数的参数个数和相应类型以及它的返回类型与基类中同名的虚函数完全一样,则无论是否为该成员函数使用virtual,它都将成为一个虚函数。
2.虚函数实现多态性的条件
产生运行时的多态性有3个前提:
(1)类之间的继承关系满足赋值兼容性规则。
(2)改写了同名虚函数。
(3)根据赋值兼容性规则使用指针或引用。
3.构造函数和析构函数调用虚函数
在构造函数和析构函数中调用虚函数采用静态联编,即它们所调用的虚函数是自己的类或基类中定义的函数,但不是任何在派生类中重定义的虚函数。
C++不支持虚构造函数,但支持虚析构函数。
4.纯虚函数
在许多情况下,不能在基类中为虚函数给出一个有意义的定义,这时可以将它说明为纯虚函数,将其定义留给派生类去做,说明纯虚函数的一般形式如下:
class 类名
{virtual 函数类型函数名(参数列表) = 0; }
一个类可以说明多个纯虚函数,包含有纯虚函数的类称为抽象类。一个抽象类只能作为基类来派生新类,不能说明抽象类的对象。
使用纯虚函数必须注意以下几个问题:
(1)纯虚函数的声明格式。
(2)含有纯虚函数的类不可以用来定义对象。
(3)当派生类中没有重新定义基类中的纯虚函数时,必须继续声明这些虚函数为纯虚函数,派生类任然是抽象类;如果派生类实现了基类的纯虚函数后,派生类就可以定义对象。
(4)含有纯虚函数的类也可以定义其他的非纯虚函数,虽然程序中不可以定义该类的对象,但还可以调用这些一般的虚函数,如派生类直接继承这些一般虚函数,便可以通过派生类的对象来调用。
多重继承是多个单一继承的组合,因此多重继承情况下啊的虚函数调用与分析单一继承有相似之处。
在派生类中,当有一个指向基类成员函数的指针指向一个虚函数,并且通过指向对象的基类指针或引用访问这个虚函数时,仍发生多态性。
(2020/11/23更新)
1、重载对象的赋值运算符
编译器在默认情况下为每个类生成一个默认的赋值操作,用于同类的两个对象之间相互辅助。但对有些类可能是不正确的,如字符串类。
重新定义运算符时,不可以违反运算符原先在C++中的使用规则,例如:纯双目运算符“/”不可以被用户定义为单目运算符。
要将运算符重载必须调用运算符函数来完成,运算符函数的形式如下:
operator op(argument_list);
其中,op是要重载的运算符(运算符必须是C++中所定义的运算符)。
例如:
str&operator=(str&a){ }
调用如:
s2=s1,
C++编译解释为:
s2.operator=(s1);
s3=s2=s1;
C++编译解释为:
s3.operator=(s2.operator=(s1));
2.运算符重载的实质
运算符重载有两种,将作为类的成员函数的重载运算符称为类运算符,而将作为类的友元的重载运算符称为友元运算符。
不能重载的运算符有:“.”,“( )”,“[ ]”,“- >”。
3.<<、>>和++运算符重载
定义形式:
ostream&operator << (ostream&output,类名&对象名)
{...//函数代码
return output;
}
调用形式:operator<<(cout ,对象名)
定义形式:
istream&operator>>(istream&input ,类名&对象名)
{...//函数代码
return intput;
}
调用形式:operator>>(cin,对象名);
class Test
{private:
int i;
float f;
char ch;
public:
friend ostream&operator<<(ostream&, Test);
friend istream&operator<<(istream&, Test);
};
ostream&operator<<(ostream&stream,Test obj)
{stream<>(istream&instr,Test&obj)
{instr>>obj.i;
instr>>obj.f;
instr>>obj.ch;
returninstr;
}
void main()
{TestA(45,8,3,'w')
operator<<(cout,A);
TestB;
operator>>(cin,B);
}
int operator ++( )//前缀++n
{...;return 返回值;}
int operator ++(int)//后缀n++,不用给出形参名
{...;return 返回值;}
友元运算符:
friend int operator ++(number&);//前缀++n
friend int operator ++(number&,int);//后缀n++,不用给出int类型的形参名
4.类运算符和友元运算符的区别
若运算符所需的操作数希望进行隐式类型转换,则运算符应通过友元来重载。
如果一个运算符的操作需要修改类对象的状态,则应当使用类运算符,这样更符合数据封装的要求。
1.流类库的基础类
图9-1 iostream流类库的基础类关系图
2.默认输入输出格式控制
关于数值数据,默认方式能够自动识别浮点数并用最短的格式输出。
字符的读入对单字符来讲,它将舍去空格,知道读到字符为止;对字符串来讲,它从读到第一个字符开始,到空格符结束。对于字符数组,使用数组名来整体读入。
Bool布尔类型,VC把输入0识别为false,其他的值均识别为1,输出只有1和0两个值。
3.使用ios_base类
(1)ios_base类派生ios类,ios又是istream和ostream的虚基类。
常量:skipws,left,dec,oct,hex,showbase,showpos,fixed等。
处理标志的成员函数:
long flags(long),long flags( ),long setf(long , long),int width( ),char fill(char),int precision(int)等。
(2)成员函数:在使用成员函数进行格式控制的时候,setf用来设置,unsetf用来恢复默认设置。他们被流对象调用。
如:
cout<
C++总共有输入文件流、输出文件流和输入输出文件流3种,并已经将它们标准化。
要打开一个输入文件流,需要定义一个ifstream类型的对象;要打开一个输出文件流,需要定义一个ofstream类型的对象;如果要打开输入输出文件流,则要定义一个fstream类型的对象。这3种类型都定义在头文件< fstream >里。
1.使用文件流
对文件进行操作的方法:
(1)打开一个相应的文件流。ofstream mystream;
(2)把这个流和响应的文件关联起来。
mystream.open("myText.txt");
可以用一条语句
ofstream myStream("myText.txt");
来完成上述的两步。
(3)操作文件流。使用<<和>>,进行文件读写。
(4)关闭文件流:文件流名.close;
2.流成员函数
(1)输出流的open函数
void open(char const * . int filemode , int = filebuf :: openprot);
形参1,表示要打开的文件名;形参2,表示文件的打开方式;形参3,文件保护方式,一般采用默认值。
打开方式:
ios_base::in 打开文件读操作
ios_base::out 打开文件进行写操作,默认模式
ios_base::app 打开文件在文件末尾添加数据
ios_base::trunc 如文件存在,将其长度阶段为0,并清除原有内容
(2)输入流类的open函数
使用默认构造函数建立对象,然后调用open成员函数打开文件。
ifstream infile; infile.open("filename" , iosmode );
也可以使用指针
ifstream * pfile; pfile->open("filename", iosmode );
使用有参构造函数
ifstream infile("filename" , iosmode );
ios_base::in 打开文件用于输入(默认)。
ios_base::binary 指定文件以二进制方式打开,默认为文本文件。
(3)close 函数:用来关闭与一个文件流相关联的磁盘文件。
(4)错误处理函数
bad( ):如果进行非法操作,返回true,否则返回false。
clear( ):设置内部错误状态,可清楚所有错误位。
eof( ):若提取操作已到文件末尾,返回true,否则返回false。
good( ):如果没有错误条件和没有设置文件结束标志,返回true,否则返回false。
fail( ):与good相反,操作失败返回false,否则返回true。
【正文完结】
余下附录内容为扩展内容,包含C++程序设计基础知识、C++自考往年考题、经典C++编程题目案例、C++实操项目等内容。
更新中