C++
C++期末复习
计算机底层 | C++ |
简介
起源
20世纪60年代,Martin Richards为计算机软件人员在开发系统软件时,作为记述语言使用而开发了BCPL语言(Basic Combined Programming Language)。1970年,Ken Thompson在继承BCPL语言的许多优点的基础上发明了实用的B语言。到了1972年,贝尔实验室的Dennis Ritchie和Brian kernighan在B语言的基础上,作了进一步的充实和完善,设计出了C语言。当时,设计C语言是为了编写UNIX操作系统的。以后,C语言经过多次改进,并开始流行。C++是在C语言的基础上发展和完善的,而C是吸收了其它语言的优点逐步成为实用性很强的语言。
C语言主要特点
- C语言是一种结构化的程序设计语言,语言本身简洁、使用灵活方便。
- 它既有高级语言的特点,又具有汇编语言的特点。运算符丰富,除了提供对数据的算术逻辑运算外,还提供了二进制的位运算。
- 程序的可移植性好。
- 程序的语法结构不够严密,程序设计的自由度大。
缺陷
C语言对数据类型检查的机制比较弱;缺少支持代码重用的结构;随着软件工程规模的扩大,难以适应开发特大型的程度等等。
C++的发展
目的
为了克服C语言本身存在的缺点,并保持C语言简洁、高效,与汇编语言接近的特点,1980年,贝尔实验室的Bjarne Stroustrup博士及其同事对C语言进行了改进和扩充,并把Simula 67中类的概念引入到C中。并在1983年由Rick Maseitti提议正式命名为C++(C Plus Plus)。后来,又把运算符的重载、引用、虚函数等功能加入到C++中,使C++的功能日趋完善。 C++语言在提供了面向对象的设计能力的同时,又保持了与C语言的高度兼容性,使C语言程序设计人员能够比较容易地转向C++语言。
👿C++对C功能的扩展包括:
- 允许使用以//开头的注释。
- 对变量的定义可以出现在程序中的任何行(但必须在引用该变量前)。
- 提供了标准输入输出流对象cin和cout,它们不用指定输入输出格式符(如%d),使输入输出更加方便。
- 可以用const定义常变量。
- 可以利用函数重载实现用同一函数名代表功能类似的函数,以方便使用,提高可读性。
- 可以利用函数模板,简化同一类的函数的编程工作。
- 可以使用带默认值的参数的函数,使函数的调用更加灵活。
- 提供变量的引用类型,即为变量提供一个别名,将“引用”作为函数形参,可以实现通过函数的调用来改变实参变量的值。
- 增加了内置函数(内嵌函数、内联函数),以提高程序的执行效率。
- 增加了单目的作用域运算符,这样在局部变量作用域内也能引用全局变量。
- 可以用string类定义字符串变量,使得对字符串的运算更加方便。
- 用new和delete运算符代替malloc和free函数,使分配动态空间更加方便。
语法
基础知识
基本概念
标识符
所谓标识符,是指用来标识程序中所用到的变量名、函数名、类型名、数组名、文件名以及符号常量名等的有效字符序列。
标识符的命名规则
由字母(大、小写皆可)、数字及下划线组成,且第一个字符必须是字母或下划线。
// 下面的标识符名是合法的
year,Day,ATOK,x1,_CWS,_change_to
// 下面的标识符名是不合法的:
#123,.COM,$100,2002Y, 1_2_3
关键字
关键字,又被称为保留字或保留关键字,它用来命名C++语言程序中的语句、数据类型和变量属性等。
基本数据类型、变量、常量
常量和变量
变量是指在程序运行过程中其值可以被改变的量。不同类型的变量在内存中占用不同的存储单元,以便用来存放相应变量的值。
基本类型
bool
布尔型bool
型变量:只能保存真、假值的变量为bool型变量。bool b1, b2;
bool
型常量:bool型常量只有两个:true和false。bool b1, b2; …… b1=true; b2=false; b1=10;//非0值 相当于: b1=true;//显示1 b1=0; 相当于 b1=false;//显示0
int
整型char
字符型- 字符变量
- 字符型变量用于存放一个单个字符
- 字符常量
- 字符型常量是由单引号括起来的一个字符。
- 字符变量
float
单精度浮点型double
双精度浮点型void
void型(或称空值类型)定义:
void型是不具有值的特殊的数据类型,它明确指出没有值的这一特性。
void型主要用在函数值类型说明以及指针类型说明等。不存在void型一般变量。
void型一般用在如下三个方面
用于定义指向任何数据类型的指针。
void *p;
如果函数没有返回值,则在函数定义时,可将返回值类型定义为void型。
void func (int n) { …… }
如果函数没有参数,则在函数定义时,其参数的位置可以放上void说明,以明确表明此函数无参数。
int func (void) { …… }
注意:不能以下述方式来定义变量 void dt;
long
、 short
、signed
、unsigned
long
和short
在C++语言中,可以在int的前面加上long和short,以定义长整型和短整型变量,同时,还可以在double的前面加上long,以定义长浮点型变量。
long int Li; short int Si; long double Ld; /* long int Li; short int Si; 可简写为一下 */ long Li; short Si;
signed
和unsigned
关键字signed有符号整型变量(可缺省)和unsigned无符号整型变量可以加在char和int等定义整型变量的关键字前面,以决定该整型变量是有符号整型变量还是无符号整型变量。
signed int si; //si是有符号整型变量 unsigned int ui; //ui是无符号整型变量 signed char sc; //sc是有符号字符型变量 unsigned char uc; //uc是无符号字符型变量 /* 由于有符号变量的最高位用于表示符号,所以无符号变量所能够表示的正数的范围将大于同一种类型的有符号变量所能表示的正数的范围。 */ char c; //c能表示的数的范围为-128 ~127 signed char sc; //sc能表示的数的范围为-128 ~127 unsigned char uc; //uc能表示的数的范围为0 ~255
枚举类型
在程序设计过程中,如果一个变量仅在很小的范围内取值,则可以把它定义为枚举类型,使用枚举类型的变量能够提高程序的可读性。
枚举变量的几种定义形式
// (1)先定义枚举类型,然后定义枚举变量
enum color {red, yellow, blue, green, white, black};
color c; // c枚举变量
/*
enum 为定义枚举类型变量的关键字
color 为枚举类型名
red、yellow和black等为枚举元素或枚举常量。
*/
// (2)定义枚举类型的同时,定义枚举变量
enum color { red, yellow } c, b;
//c、b 就是color类型的枚举变量。
// (3)直接定义枚举变量
enum { red, yellow } c;
// c为枚举变量,其取值范围仅限于枚举元素表中的值(即枚举常量)。
使用枚举类型数据时注意如下几个问题:
- 枚举元素都是常量,而不是变量,不能为枚举元素赋值。
- 每个枚举元素都有一个确定的整数值,可以在枚举类型定义时显式地给出枚举元素的值,若缺省则按顺序依此为0,1,2,……。
- 可以将一个整数经强制类型转换后赋给枚举变量。
- 枚举常量可以直接赋给整型变量。
const
关键字
const关键字主要用来定义其数值不能改变的变量(所定义的仍然是变量)
typedef
关键字
使用typedef关键字来定义新的类型名。
typedef int word;
typedef unsigned char byte;
/*
它表示可以用word来定义int型变量,可以用byte来定义unsigned char型变量。
*/
注意:
- typedef 关键字不能创造新的类型,只能为已有的类型增加一个类型名。
- typedef 关键字只能用来定义类型名,而不能用来定义变量。
类型转换
在进行整数、浮点数等数据的混合运算时,不同类型的数据要首先转换为同一种类型的数据,然后才能进行运算,而这种转换最终都归结为整数和浮点数之间的转换问题。
自动转换
C++语言中的二元运算符(如算术运算符和关系运算符等)要求两个操作数的类型一致。如果类型不一致,则编译系统将进行自动类型转换。自动类型转换又被称为隐含类型转换,其转换规则与所使用的运算符有关。
- 规则
- 当单、双精度浮点型数据赋给整型变量时,浮点数的小数部分将被舍弃。
- 当整型数据赋给浮点型变量时,在数值上不发生任何变化。
- 如果某个算术运算符的两个运算对象都是整数,那么,运算将按照整型数的运算规则来进行。
- 只要某个算术运算符的两个运算对象中有一个是浮点型数据,则运算将按照浮点数的运算规则来进行。
强制类型转换
当自动类型转换达不到目的时,可以利用强制类型转换。
强制类型转换的形式:
(类型名) 表达式
// or
类型名 (表达式)
注意:经强制类型转换后,得到的是一个所需类型的中间变量,原来变量的类型及其数值大小并没有发生任何变化。
运算符
C++语言中的运算符很多,主要包括算数运算符、关系运算符、逻辑运算符、位运算符以及增1减1运算符等。正确掌握这些运算符的使用方法是非常重要的。
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
- 杂项运算符
算术运算符
运算符 | 描述 |
---|---|
+ | 把两个操作数相加 |
- | 从第一个操作数中减去第二个操作数 |
* | 把两个操作数相乘 |
/ | 分子除以分母 |
% | 取模运算符,整除后的余数 |
++ | 自增运算符,整数值增加 1 |
-- | 自减运算符,整数值减少 1 |
关系运算符
运算符 | 描述 |
---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 |
<= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 |
逻辑运算符
运算符 | 描述 |
---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 |
` | |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 |
位运算符
运算符 | 描述 |
---|---|
& | 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。 |
` | ` |
^ | 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。 |
~ | 二进制补码运算符是一元运算符,具有"翻转"位效果,即0变成1,1变成0。 |
<< | 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。 |
>> | 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。 |
赋值运算符
运算符 | 描述 |
---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 |
<<= | 左移且赋值运算符 |
>>= | 右移且赋值运算符 |
&= | 按位与且赋值运算符 |
^= | 按位异或且赋值运算符 |
!= | 按位或且赋值运算符 |
其他运算符
运算符 | 描述 |
---|---|
sizeof | sizeof 运算符返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。 |
Condition ? X : Y | 条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。 |
, | 逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。 |
. (点)和 -> (箭头) | 成员运算符用于引用类、结构和共用体的成员。 |
Cast | 强制转换运算符把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。 |
& | 指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。 |
* | 指针运算符 * 指向一个变量。例如,*var; 将指向变量 var。 |
条件运算符
Condition ? X : Y
条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。
成员访问运算符
.
(点)和 ->
(箭头) 成员运算符用于引用类、结构和共用体的成员。
运算符优先级
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | ^ | 从左到右 |
位或 OR | | | 从左到右 |
逻辑与 AND | && | 从左到右 |
逻辑或 OR | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
基本语句
- if语句
if(表达式)
语句1
else
语句2
- switch语句
switch(表达式)
{ case 常量表达式1:
语句1
case 常量表达式2:
语句2
…
case 常量表达式n:
语句n
default:
语句n+1
}
/*注意:
(1)switch后面圆括号内的表达式的值和case后面的常量表达式的值,都必须是整型的或字符型的,不允许是浮点型的。
(2)同一个switch语句中的所有case后面的常量表达式的值都必须互不相同。
(3)switch语句中的case和default的出现次序是任意的。
(4)switch语句中的“case 常量表达式”部分只起语句标号的作用, 而不进行条件判断。
(5)每个case后面也可以是多个语句,不需要用花括号。
(6)多个case的后面可以共用一组执行语句。
*/
- while语句
while(){
// 语句(即循环体部分)
}
/*注意:
(1)while语句的特点是先判断表达式的值,然后执行循环体中的语句,因此,如果表达式的值一开始就为“假”,则循环体将一次也不执行。
(2)当循环体由多个语句组成时,必须用左、右花括号括起来,使其形成复合语句。
(3)为了使循环最终能够结束,而不至于产生“死循环”,每执行一次循环体,表达式的值都应该有所变化,这既可以在表达式本身中实现,也可以在循环体中实现。
*/
- for语句
for(){
// 循环体
}
- do-while语句
do
// 循环体
while():
/*注意
(1)由于do-while语句是先执行循环体,然后再判断表达式的值,所以,无论一开始表达式的值为“真”还是为“假”,循环体中的语句都至少被执行一次,这一点同while语句是有区别的。
(2)如果do-while语句的循环体部分是由多个语句组成的话,则必须用左、右花括号括起来,使其形成复合语句。
*/
break
和continue
break
- 其执行过程是:终止对switch语句或循环语句的执行,即跳出这两种语句,而转入下一语句执行。
- 注意:
- break语句只能用于循环语句或switch语句中。
- 在循环语句嵌套使用的情况下,break 语句只能跳出(或终止)它所在的循环,而不能同时跳出(或终止)多层循环。
continue
- 其执行过程是:终止当前这一轮循环,即跳过循环体中位于continue后面的语句而立即开始下一轮循环:对于while和do-while语句来讲,这意味着立即计算循环是否终止的表达式(即执行条件测试部分),而对于for语句来讲,这意味着立即计算表达式3。
goto语句
- 一般形式
goto 标号;
- 标号一般形式
标号: 语句;
- 一般形式
return
return 返回值;
数据的输入和输出
C++程序在执行期间,接收外部信息的操作称为程序的输入;而把程序向外部发送信息的操作称为程序的输出。在C++中没有专门的输入输出语句,所有输入输出是通过输入输出流来实现的。
//(1)C语言中的函数:如scanf( )、printf( )等需要包含“stdio.h”文件
//(2)C++语言中特有的功能需要包含“iostream.h”文件
流的类型
文本流:文本流是一个字符序列。
二进制流:二进制流是一组字节序列。
<<
和 >>
的输入输出
基于运算符 cin
:标准输入流- 在缺省的情况下,cin自动跳过输入的空格,换言之,cin不能将输入的空格赋给字符型变量,同样地,回车键也是作为输入字符之间的分隔符,也不能将输入的回车键字符赋给字符型变量。
// 若要把从键盘上输入的每一个字符,包括空格和回车键都作为一个输入字符赋给字符型变量时,必须使用函数cin.get()。其格式为: // cin.get(<字符型变量>); char c1; cin.get(c1); /* 在缺省的情况下,系统约定输入的整型数是十进制数据。当要求按八进制或十六进制输入数据时,在cin中必须指明相应的数据类型: hex为十六进制;oct为八进制;dec为十进制。 */ int i,j,k,l; cin>>hex>>i; //指明输入为十六进制数 cin>>oct>>j; //指明输入为八进制数 cin>>k; //输入仍为八进制数 cin>>dec>>l; //指明输入为十进制数 // 当执行到语句cin时,若输入的数据为: // 11 11 12 12<CR>
cout
:标准输出流// 指定输出项占用的宽度: // setw(6)指明其后的输出项占用的字符宽度为6 /* 使用setw()应注意以下三点: 1、在程序的开始位置必须包含头文件iomanip.h,即在程序的开头增加:#include <iomanip.h> 2、括号中必须给出一个表达式(值为正整数),它指明紧跟其后输出项的宽度。 3、该设置仅对其后的一个输出项有效。一旦按指定的宽度输出其后的输出项后,又回到原来的缺省输出方式。 格式化输出: (1)域宽的指定width( ) (2)填充文字的指定fill( ) (3)精度的指定precision( ) a) 在输出浮点数时,可以利用cout对象的precision( )成员函数来指定所要输出的浮点数的位数(包 括整数部分的位数和小数部分的位数,但不包含小数点)。 (4)输出的刷新flush( ) a) flush( )成员函数用于刷新输出缓冲区。 */ cout.width(0); // 此函数执行了以后,输出时的宽度就采用缺省值,也就是按照实际所需要的宽度来进行显示。 cout.fill(‘#’); // 表示不足的位置由‘#’填充 // 输出八、十六进制数和科学表示法的实数 cout.setf(ios::scientific,ios::floatfield); //表明浮点数用科学表示法输出
cerr
:非缓冲型的标准出错流clog
:缓冲型的标准出错流
字符的输入输出
输入
get()
是cin对象的成员函数,用于输入操作。getline( )
- 注意: 对于字符串输入来讲,get( )和getline( ) 函数的功能基本相同,但get( )函数并不读入由delim标识的区分字符,而getline( )函数读入由delim标识的区分字符。
输出
put()
是cout对象的成员函数,用于输出操作。
文件流
ifstream // 文件输入用流类
ofstream // 文件输出用流类
fstream // 文件输入输出用流类
// 上述类是在fstream.h中定义的,因此在程序首部需利用#include指令包含进fstream.h头文件。
文件的打开和关闭
打开文件时一般使用成员函数open( ),关闭文件时使用成员函数close( )
// 输入用的open成员函数的格式如下:
void open(const char *fname, int openmode=ios::in, int prot=filebut::openprot);
// 输出用的open成员函数的格式如下:
void open(const char *fname, int openmode=ios::out,int prot=filebut::openprot);
/*
fname:指向文件名的指针;
openmode:打开模式;
prot:打开文件的保护种类(一般采用缺省值)。
*/
数组
数组是一些具有相同类型的数据的集合,它是由某种类型的数据按照一定的顺序组成的。同一个数组中的每个元素都具有相同的变量名,但具有不同的序号(下标)。C++语言允许使用任意维数的数组。数组同其它类型的变量一样,必须先定义、后使用。
一维数组
// 一维数组的定义
类型说明符 数组名[常量表达式];
// 类型说明符可以是 int、char和float等;
// C++不允许对数组的大小作动态的定义,即数组的大小不能是变量,必须是常量。
注意:
表示数组长度的常量表达式,必须是正的整型常量表达式,通常是一个整型常量。
C++语言不允许定义动态数组,也就是说,数组的长度不能依赖于程序运行过程中变化着的变量。
相同类型的数组、变量可以在一个类型说明符下一起定义,互相之间用逗号隔开。
二维数组
// 定义
类型说明符 数组名[常量表达式][常量表达式];
指针
指针在C++语言中占有重要的地位,同时指针也是C++语言的一大特点。C++语言的高度灵活性及其特别强的表达能力,在很大程度上来自于巧妙而恰当地使用指针。C++语言的指针既可以指向各种类型的变量、对象,也可以指向函数。正确地使用指针,能够有效地表示和处理复杂的数据结构,特别有利于对动态数据的管理。
基本概念:
- 直接访问
- 按变量地址存取变量的值。cin>>i; 实际上放到定义 i 单元的地址中。
- 间接访问
- 将变量的地址存放在另一个单元p中,通过 p 取出变量的地址,再针对变量操作。
一个变量的地址称为该变量的指针。
// 类型标识符 *变量名
int *i_point;
/*
一个指针变量只能指向同一类型的变量。即整型指针变量只能放整型数据的地址,而不能放其它类型数据的地址。
* 在定义语句中只表示变量的类型是指针,没有任何计算意义。
* 在语句中表示“指向”。&表示“地址”。
*/
指针变量的引用
&
取地址运算符,作用是取得变量所占用的存储单元的首地址。在利用指针变量进行间接访问之前,一般都必须使用该运算符将某变量的地址赋给相应的指针变量。
*
间接访问运算符,作用是通过指针变量来间接访问它所指向的变量(存数据或取数据)。
const
指针
// 1. 指向常量的指针(常量指针)
// 在指针定义语句的类型前加const,表示指向的对象是常量。
const int a=18;
const int *pi=&a;
// 2. 指针常量
// 在指针定义语句的指针名前加const,表示指针本身是常量。
char * const pc=“abcd”; //指针名前加const,定义指针常量
// 注意:Pc是指针常量,在定义指针常量时必须初始化,就像常量初始化一样,这里初始化的值是字符常量的地址。指针常量pc是常量,不能作为左值进行操作,但是允许修改间接访问值,即*pc可以修改。
// 3. 指向常量的指针常量(常量指针常量)
// 可以定义一个指向常量的指针常量,它必须在定义时进行初始化。
const int ci=8;
const int *const cpi=&ci;//指向常量的指针常量
void 型指针
在C语言中,void型指针同其它类型的指针可以互相赋值,但是在C++语言中,要想将void型指针赋给其它类型的指针变量,必须进行强制类型转换。
char *pc;
void *pv;
…
pv=pc; //C语言和C++语言皆可
pc=pv; //C语言可以,但C++语言不行
pc=(char *)pv; //C语言和C++语言皆可
char *pc
…
pc=(char *) malloc (100); //C语言和C++语言皆可
pc=malloc(100); //C语言可以,但C++语言不行
// malloc( ) 函数是用于内存申请的,此函数的返回值是指向申请到的内存的void型指针。
二级指针
如果一个变量的值是一个一级指针变量的地址,则称这个变量是二级指针变量。
// 定义
// 类型说明符 **变量名;
int p, *p1, **p2;
p=10; p1=&p; p2=&p1;
/*
p是整型变量,
p1是指向整型变量的一级指针变量,
p2是指向整型一级指针变量的二级指针变量。
变量p、p1和p2之间的关系:
*p1等价于p,*p2等价于p1,即**p2等价于p。
*/
指针和数组
在C++语言中,指针和数组的关系非常密切,引用数组元素既可以通过下标也可以通过指针来进行。正确地使用数组的指针来处理数组元素,能够产生高质量的目标代码。
// (1)指向数组元素的指针及其操作
// 若
int a[10];
int *ip;
// 则
ip=&a[0];
// 把a[0]元素的地址赋给指针变量ip,即ip指向a数组的第0号元素。
ip=&a[i];
// 将使ip指向a数组的第i号元素。
// 指向数组元素的指针变量的定义与赋值
int a[10], *p;
p=&a[0];
p=a;
// p是变量,a为常量。
// 若数组元素为int型,则指向其的指针变量也应定义为int型。
// 这两种情况均为赋初值
int a[10];
int *p=a; int *p=&a[0];
指针和一维数组
// 1. 指向数组元素的指针及其操作
// 1)数组名就代表该数组的第0号元素的地址。
ip=&a[0];
ip=a;
// 2)当ip指向数组的某一元素时,无论该数组是何种类型,ip+1都指向数组的下一个元素。
a[5]=10;
*(ip+5)=10;
*(a+5)=10;
// 2. 数组名和函数参数
/*
当数组名作函数参数时,在函数调用时,实际传递给函数的是该数组的起始地址,即指针值,而不是数组元素。这样,在函数形参说明中,就可以将数组形参说明为指针,并可以在函数体中通过指针来存取数组中的元素。这种处理方式通常用在对字符串(以“\0”结尾的字符数组)的处理上。
*/
// 3. 字符串和指针
// 利用字符数组来存取字符串。
char str[ ]={"This is a string."};
/*
在程序中每使用一个字符串常量(用双引号括起来的字符序列),C++语言编译系统就产生一个指向该字符串的指针,这样,在程序中就可以通过字符指针来处理字符串,而不必使用字符数组。
*/
// 定义一个字符类型指针sp;
char *sp;
// 通过赋值语句,将字符串的指针存入sp中:
sp="This is a string.";
// 显示由sp所指向的字符串
cout << sp;
// 显示sp所指向的字符串中的“string”部分,
sp=sp+10;
cout << sp;
// (4) 指针数组
// 如果一个数组的每个元素都是指针类型的数据,则这种数组被称为指针数组。
// 指针数组的定义方式:
类型说明符 *数组名[常量表达式];
//(5)命令行参数
// 命令行参数:一般是指在操作系统状态下所输入的命令及其参数。
// 带参数的命令一般具有如下形式:
命令名 参数1 参数2 … 参数n
注意:
- 在初始化方面,数组和指针是有区别的。
- 在赋值方面,字符数组只能一个元素一个元素地赋值,而不能一次为整个数组赋值。
- 字符数组定义了之后,编译系统也就为它分配了一段内存单元,其首地址可由数组名来表示,以后就可以利用这段内存单元来存取字符数组中的每一个元素;而指针变量定义了之后,编译系统仅为它分配了一个用于存放指针值(地址)的单元,而它具体指向的内存单元并未确定,要想利用它来间接访问某一个字符,就必须首先将该字符变量的地址赋给它。
- 指针变量的值是可以改变的,它可以指向字符串中的任意位置上,是可以作为赋值对象出现的;而数组名尽管代表数组的首地址,但它是常量,其值只能够利用而不能被改变,不能作为赋值对象出现。
结构体、共同体和枚举类型
结构体
将不同种类型的数据有序地组合在一起,构造出一个新的数据类型,这种形式称为结构体。
// 结构体是多种类型组合的数据类型。
struct 结构体名
{
// 成员列表
};
// 定义结构体类型变量的方法
// 一、先定义结构体类型再定义变量名
struct student
{ int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};
struct student student1, student2;
/*
结构体类型只是一种数据类型,不占内存空间,只有定义结构体类型变量时才开辟内存空间。
*/
// 二、在定义类型的同时定义变量
struct 结构体名
{
//成员列表
}变量名列表;
struct student
{ int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
} student1,student2; // 紧接着定义变量
注意:
结构体类型的变量在内存依照其成员的顺序顺序排列,所占内存空间的大小是其全体成员所占空间的总和。
在编译时,仅对变量分配空间,不对类型分配空间。
对结构体中各个成员可以单独引用、赋值,其作用与变量等同。
格式: 变量名 . 成员名 student1 . num
结构体的成员可以是另一个结构体类型。
成员名可以与程序中的变量名相同,二者分占不同的内存单元,互不干扰。例如,在程序中仍可以定义变量int num;
结构体类型变量的引用
// 不能对结构体变量整体赋值或输出,只能分别对各个成员引用。 cin>>student1; cin>>student1.num; student1.num=100; // 嵌套的结构体变量必须逐层引用。 student1.birthday.day=25; // 结构体变量中的成员可以同一般变量一样进行运算。 student1.birthday.day++; student1.score+=60;
关于结构类型变量的使用,说明以下几点:
同类型的结构体变量之间可以直接赋值。这种赋值等同于各个成员的依次赋值。
结构体变量不能直接进行输入输出,它的每一个成员能否直接进行输入输出,取决于其成员的类型,若是基本类型或是字符数组,则可以直接输入输出。
结构体变量可以作为函数的参数,函数也可以返回结构体的值。当函数的形参与实参为结构体类型的变量时,这种结合方式属于值调用方式,即属于值传递。
共用体
C++语言中,允许不同的数据类型使用同一存储区域,即同一存储区域由不同类型的变量共同表示。这种数据类型就是共用体。
union 共用体名
{
成员表列;
} 变量表列;
// 共用体变量的引用
// 不能整体引用共用体变量,只能引用变量中的成员。
特点:
- 共用体的空间在某一时刻只有一个成员在起作用。
- 共用体变量中的成员是最后一次放入的成员。
- 共用体变量不能在定义时赋初值。
- 共用体变量不能作为函数的参数或函数值,但可使用指向共用体的指针变量。
- 共用体可以作为结构的成员,结构体也可以作为共用体的成员。
枚举类型
如果一个变量只有几种可能的值,可以定义为枚举类型。
枚举类型就是将变量的值一一列举出来,变量的值仅限于列举出来的值的范围内。
enum weekday {sun, mon, tue, wed, thu, fri, sat};
enum weekday workday, weekend ;
// 另一种定义变量的方法
enum {sun, mon, tue, wed, thu, fri, sat} workday, weekend;
// 其中sun, mon,....,sat称为枚举元素或枚举常量,为用户定义的标识符,所代表的意义由用户决定,在程序中体现出来。
// 注意:
// 1、枚举元素为常量,不可赋值运算。 sun=0; mon=1;
// 2、在定义枚举类型的同时,编译程序按顺序给每个枚举元素一个对应的序号,序号从0开始,后续元素依次加1。
enum weekday {sun, mon, tue, wed, thu, fri, sat};
// 3、可以在定义时人为指定枚举元素的序号值。
enum weekday {sun=9, mon=2, tue, wed, thu, fri, sat};
// 4、只能给枚举变量赋枚举值,若赋序号值必须进行强制类型转换。
day=mon;
day=1;
day=(enum weekday)1;
// 5、枚举元素可以用来进行比较判断。
if (workday= = mon) if (workday>sun)
// 6、枚举值可以进行加减一个整数n的运算,得到其前后第n个元素的值。
workday=sun;
workday=(week)(workday+2);
// 7、枚举值可以按整型输出其序号值。
workday=tue;
cout<<workday;
void main(void)
{
enum team{ qiaut, cubs=4, pick, dodger=qiaut-2;};
cout<<qiaut<<‘\t’<<cubs<<‘\t’;
cout<<pick<<‘\t’<<dodger<<endl;
}
// 输出:0 4 5 -2
正式入门
类和对象的特性
面向对象的程序设计
概念解释
- 对象——构成系统的基本单位
- 对象具有属性和行为两个要素,属性表示对象的静态特征,对应于数据,在程序中用变量表示;行为表示对象的动态特征,对应于成员函数可以实现对象的某些功能。
- 外部程序使用对象是通过调用这个对象的成员函数来实现的。
- 封装性——面向对象程序设计方法的一个重要特点
- 将有关的数据和操作代码封装在一个对象中,形成一个基本单位,各对象之间相对独立,互不干扰。
- 将对象中某个部分对外隐蔽(设置成private或protected),即隐蔽其内部细节,只留下少数接口(设置成public),以便与外界联系,接收外界信息,这种对外界隐蔽的做法也称为信息隐蔽。信息隐蔽有利于数据安全,防止类外无关代码修改数据
- 抽象——将有关事物的共性归纳、集中的过程。
- 抽象的过程是
- 抽象的作用是表示同一类事物的本质。
- 类是对象的抽象,而对象则是类的特例,或者说对象是类的具体表现形式。
- 继承与重用
- 在已有的类的基础上,增加一些特有的属性和方法,形成一个新类,原有的类叫基类或父类,新生成的类是基类的派生类或父类的子类。
- C++提供了继承机制,可利用自己建立过的类或别人使用的类或存放在类库中的类,可减少工作量,这种方法也可以称为软件重用。
- 多态性
- 多态现象:有几个相似而不完全相同的对象,当向它们发出同一个消息时,它们的反应各不相同,分别执行不同的操作。
- 多态性:在C++中,多态性指由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应。
- 多态性是面向对象程序设计的重要特征,能增加程序的灵活性
面向对象的程序设计特点
- 面向过程的程序设计----函数是构成系统的基本单位
- 面向对象的程序设计----对象是构成系统的基本单位
面向对象程序设计方法设计系统的任务:
- 设计所需的各种类和对象,即决定把那些数据和操作封装在一起;
- 考虑怎样向有关对象发送消息,以完成所需的任务。
👿类和对象的含义:
- 类的含义:
- 类是对一类事物属性与行为的抽象,它属于该类的所有对象提供了统一的抽象表述,其内部包括属性和行为两部分。在面向对象的编程语言中,类是一个独立的程序单位,他应该有一个类名,并且包括属性说明和服务说明两个重要部分。
- 对象的含义:
- 对象是一个动态的概念,是用来描述客观事物的一个实体,它是构成系统的一个基本单位。对象是类的具体个体,一个对象由一组属性和对这组属性进行的操作组成。
类和对象的关系:类是对象的抽象,而对象是类的实例。
类的声明和对象的定义
类
// 声明定义
// 类的定义格式:
class 类名
{ private:
成员数据;
成员函数;
public:
成员数据;
成员函数;
protected:
成员数据;
成员函数;
};
定义类时注意:
- 类具有封装性,并且类只是定义了一种结构(样板),所以类中的任何成员数据均不能使用关键字
extern
,auto
或register
限定其存储类型。 - 在定义类时,只是定义了一种导出的数据类型,并不为类分配存储空间,所以,在定义类中的数据成员时,不能对其初始化
对象
定义对象
在定义类时,只是定义了一种数据类型,即说明程序中可能会出现该类型的数据,并不为类分配存储空间。只有在定义了属于类的变量后,系统才会为类的变量分配空间。类的变量我们称之为对象。
对象是类的实例,定义对象之前,一定要先说明该对象的类。
// 定义对象
// 1. 先声明类类型,再定义对象
// class 类名 对象名;
class Student stu1,stu2; // 示例
// 类名 对象名; // 示例
Student stu1,stu2;
// 2. 在声明类的同时定义对象
class Student
{
public:
void display()
{cout<<“name:”<<name<<endl;}
private:
char name[20];
}stu1,stu2;
// 在定义Student类的同时,定义了两个Student类的对象。
// 3. 不出现类名,直接定义对象----语法允许,但一般不用
class
{
public:
……
private:
……
}stu1,stu2;
// 一般情况下,类的声明和类的使用是分开的,常把一些常用的功能封装成类,并放在类库中使用,第一种方法最常用。
// 定义全局和局部对象
// 对象的定义方法同结构体定义变量的方法一样,也分三种,当类中有数据成员的访问权限为私有时,不允许对对象进行初始化。
// 示例
class A {
float x,y;
public:
void Setxy( float a, float b ){ x=a; y=b; }
void Print(void) { cout<<x<<‘\t’<<y<<endl; }
} a1,a2; // 定义全局对象
void main(void)
{ A a3,a4; // 定义局部对象
}
对象的使用
一个对象的成员就是该对象的类所定义的成员,有成员数据和成员函数,引用时同结构体变量类似,用.
运算符。
- 用成员选择运算符
.
只能访问对象的公有成员,而不能访问对象的私有成员或保护成员。若要访问对象的私有的数据成员,只能通过对象的公有成员函数来获取。
同类型对象可以整体赋值
同类型的对象之间可以整体赋值,这种赋值与对象的成员的访问权限无关。
// 示例
class A {
float x,y;
public:
float m,n;
void Setxy( float a, float b ){ x=a; y=b; }
void Print(void) { cout<<x<<‘\t’<<y<<endl; }
};
void main(void)
{ A a1,a2;
a1.m=10; a1.n=20; //为公有成员数据赋值
a1.Setxy(2.0,5.0);
a2=a1; // 同类型的对象之间可以整体赋值 | 相当于成员数据间相互赋值
a1.Print(); a2.Print();
}
类和结构体的异同
在C++中,用struct声明的结构体类型实际上就是类。
- 用struct声明的类,如果对其成员不作private或public声明,系统将其默认为public。如果想分别指定私有成员和公有成员,则应用private或public作显示声明;
- 用class定义的类,如果不作private或public声明,系统将其成员默认为private,在需要时可以用显示声明改变。
类的成员函数
性质
作用域
成员函数可以访问本类中的任何成员(公有、私有、受保护),可以引用在本作用域中有效的数据。
- public成员函数:可以被本类中的其他成员函数及在类外调用。
- private成员函数:只能被本类中的其他成员函数调用。
- protected成员函数:能被本类和子类中的成员函数调用。
- 一般,将需要被外界调用的成员函数设为public----类的对外接口。
类外定义成员函数
- 类外定义成员函数,类函数必须在类内作原型声明,再在类外定义,类体的位置应该在函数定义之前,否则编译时会出错。函数名前加类名和作用域运算符
::
,例如:Student::display()
表示Student类作用域中的函数,也就是Student类成员函数。 - 虽然函数在类外定义,但在调用成员函数时会根据类中声明的函数原型找到函数的定义(函数代码),从而执行该函数。
总结:成员函数体小,在类内定义;成员函数体大,在类外定义;
内置成员函数 (inline
成员函数)
在类内定义的不包括循环的成员函数,系统自动按内联函数处理;
在类体外定义的成员函数,系统不作内联函数处理,如需要,需作inline
显示声明。(只有规模小、调用频率高,设为内联函数才有意义)
👿 宏与内联函数的区别
- 内联函数采用的是值传递,而宏定义采用的是对等替换
- 宏是由预处理器对宏进行替换;而内联函数是通过编译器控制来实现
- 宏定义不是真正的函数,没有参数类型检查,不安全,而内联函数是真正的函数,更安全
- 宏定义是要任意参数,一般用括号括起,否则容器出现二义性,而内联函数不会
成员函数的存储方式
- 每个对象所占用的存储空间只是该对象的数据成员所占用的存储空间,而不包括函数代码所占用的存储空间。
- 无论成员函数是在类内还是类外定义,是否为内联函数,均不占用对象的存储空间。
- 不用对象调用同一段函数代码,通过this指针,指向不同对象,输出不同结果。
- 从逻辑的角度,成员函数和数据封装在一个对象中,只允许本对象的成员函数访问同一对象的私有数据。
如何访问对象成员
通过对象名和成员运算符访问对象中的成员
// 访问对象中的成员(变量或函数)的一般形式:
// 对象名.成员名
// 例如:
Stu1.num //(成员数据)或
Stu1.display() //(成员函数)
// 在类外只能调用公有的成员函数。在一个类中应当至少有一个公用的成员函数,作为对外的接口,否则就无法对对象进行任何操作。
通过指向对象的指针访问对象中的成员
class Time
{public:
int hour;
int minute;
};
Time t,*p; //定义对象t和指针变量p,
P=&t; //p指向对象t,*p=t
cout<<p->hour; //输出p指向的对象中的成员hour
通过对象的引用来访问对象中的成员
class Time
{public:
int hour;
int minute;
};
Time t1; //定义对象t1
Time &t2=t1; //定义Time类引用t2,并使之初始化为t1
cout<<t2.hour; //输出t1中的成员hour
修饰符
public
:- 用关键字public限定的成员称为公有成员,公有成员的数据或函数不受类的限制,可以在类内或类外自由使用;对类而言是透明的。
protected
:- 而用关键字protected所限定的成员称为保护成员,只允许在类内及该类的派生类中使用保护的数据或函数。即保护成员的作用域是该类及该类的派生类。
private
- 用关键字priviate限定的成员称为私有成员,对私有成员限定在该类的内部使用,即只允许该类中的成员函数使用私有的成员数据,对于私有的成员函数,只能被该类内的成员函数调用;类就相当于私有成员的作用域。
总结:
私有成员 | 公有成员 | 保护成员 | |
---|---|---|---|
类内函数 | 可以调用 | 可以调用 | 可以调用 |
类外函数 | 不可调用 | 可以调用 | 不可调用 |
额外的
- 对象可以作函数的入口参数(实参、形参),也可以作函数的出口参数。这与一般变量作为函数的参数是完全相同的。
- 可以定义类类型的指针,类类型的引用,对象数组,指向类类型的指针数组和指向一维或多维数组的指针变量
- 一个类的对象,可作为另一个类的成员
类声明和成员函数定义的分离 一个C++程序由3个部分组成:
- 类声明头文件(后缀为.h或无后缀);
- 类实现文件(后缀为.cpp),包括类成员函数的定义;
- 类的使用文件(后缀为.cpp),即主文件。
基类与派生类
在派生类外部(派生类用户)使用基类成员时: 不同的继承方式决定了基类成员在派生类中的访问属性, 从而对派生类用户的访问权限产生影响。
public
继承, 所有基类成员在派生类中保持原有的访问级别。protected
继承, public–protected, protected-protected,private-private。private
继承, 所有基类成员在派生类中变为private成员。
构造函数与析构函数
构造函数
构造函数主要用于对类对象中的数据成员进行初始化
构造函数的特征:
构造函数的名字与类名相同。
造函数只是用于为数据成员设置初始值而没有返回值,因此,构造函数没有返回值类型说明。
构造函数是在生成类对象时自动调用的。
#include <iostream> class cExam { int aInt; public: cExam( ); //构造函数 void disp( ) { cout << “aInt=” << aInt << ‘\n’;} }; cExam::cExam() // 类外定义构造函数 { aInt=999; } void main( ) { cExam dt; dt.disp( ); } // 结果 aInt=999 /* (1)构造函数cExam()没有参数,故将其称为 无参构造函数 或 缺省构造函数 。 (2)当构造函数的定义比较短时(不是绝对的),也可以在类中进行说明的同时进行定义。 */ // 例如,上面cExam类可改为如下形式: class cExam { int aInt; public: cExam( ) { aInt = 999; } void disp( ) { cout << “aInt=” << aInt << ‘\n’; } };
注意:
- 在建立类对象时会自动调用构造函数。
- 构造函数没有返回值,它的作用只是对对象进行初始化。因此也不需要在定义构造函数时声明类型,这是它和一般函数的一个重要的不同之点。
- 构造函数不需要用户调用,也不能被用户调用。构造函数是在定义对象时由系统自动执行的,而且只能执行一次。构造函数一般声明为public。
- 可以用一个类对象初始化另一个类对象。
- 在构造函数的函数体中不仅可以对数据成员赋初值,而且可以包含其他语句,例如cout语句。但是一般不提倡在构造函数中加入与初始化无关的内容,以保持程序的清晰。
- 如果用户自己没有定义构造函数,则C++系统会自动生产一个构造函数,只是这个构造函数函数体是空的,没有参数,不执行初始化操作。
- 构造函数有不同的形式以便用于不同情况的数据初始化。
带有参数的构造函数
// 构造函数格式:
构造函数名(类型1 形参1,类型2 形参2,…)
// 定义对象的一般格式:
类名 对象名(实参1,实参2,…)
/*
通常情况下,在构造函数的函数体内通过赋值语句对数据成员实现初始化。
C++还提供了一种更简单的方法---参数初始化表来实现对数据成员的初始化。
这种方法不在函数体内对数据成员初始化,而是在函数首部实现。
*/
// 带有参数初始化表的构造函数的一般形式:
类名:: 构造函数名(参数表)[:成员初始化表]
{
[构造函数体]
}
// 示例
Box::Box(int h,int w):height(h),width(w){ }
// 注意:如果数据成员是数组,则应在构造函数的函数体中用语句对其赋值,不能在参数初始化表中对其初始化。
- 带参数的构造函数中的形参,其对应的实参是在建立对象时给定的,即在建立对象时指定数据成员的初值。
- 定义不同对象时用的实参是不同的,它们反映不同对象的属性。用这种方法可以方便地实现对不同的对象进行不同的初始化。
构造函数显示调用
构造函数一般都是隐式调用的,即在定义类对象时自动地调用相应的构造函数,但由于构造函数也同一般的成员函数一样,因此在需要时也可以显式地调用相应的构造函数。
// (1)构造函数的显式调用是在等号的后面直接给出构造函数名及其参数,其形式如下:
cXydata dt2 = cXydata(300,400);
// (2)一般来讲,构造函数的显式调用和隐式调用的效果是一样的,通常使用隐式调用的方式比较多。
构造函数的重载
构造函数同一般函数一样,也可以进行重载,也就是在一个类中可以定义多个重名的构造函数。下面的例子给出了重载构造函数的定义方式。
注意:
// 构造函数如下
cXydata( ); //构造函数1
cXydata( int a ); //构造函数2
cXydata( int a, int b ); //构造函数3
// 当需要调用具有一个参数的构造函数时,可以直接采用下面两种方式中的任一个:
cXydata dt2(100);
cXydata dt3=200;
//(2)当需要调用无参数构造函数时,下面的写法是错误的:
cXydata dt1( );
// 这是因为上述定义表示dt1是返回值类型为cXydata的函数,而不是对象名,因此在定义时需要去掉括号。
//(3)构造函数的重载同一般函数的重载一样,也需要满足相应的重载条件。
缺省构造函数
每一个类中都必须要有构造函数,如果程序中没有定义构造函数,则系统将自动生成一个缺省构造函数,在此缺省构造函数中将为每个数据成员设置一个随机数。因此,为了使每个对象中的数据成员都能够在定义时有一个有意义的值,在程序中应该定义构造函数。需要注意的是,如果一个类中已经定义了构造函数,则无论该构造函数是不是缺省构造函数,系统都不会再生成一个缺省构造函数。
// 没有参数的构造函数被称为缺省构造函数。缺省构造函数的说明如下所示:
cXydata( );
// 所有参数都可以省略的构造函数也可以成为缺省构造函数。例如:
/*
此时,若以无参形式来生成对象时,上面两个构造函数都将充当缺省构造函数的角色,
因此,上述两种形式的构造函数是不能同时存在的。
以下面的形式来定义对象时,将自动调用缺省构造函数:
*/
cXydata dt1;
👿 构造函数的重载与覆盖的区别:
- 函数重载发生在同一个类的内部。函数覆盖发生在子类和父类之间。
- 函数重载是同一类的不同方法,函数覆盖是不同类的同一方法。
- 重载函数的参数列表不同,覆盖函数的参数列表相同
- 重载函数调用时根据参数类型选择方法、覆盖函数调用时根据对象类型选择方法
他们与多态之间的关系
- 函数重载与覆盖是实现多态的手段,其中重载是静态多态的实现,覆盖是动态多态实现
复制构造函数
解决问题
- 生成一个对象的副本有两种途径,第一种途径是建立一个新对象,然后将一个已有对象的数据成员值取出来,一一赋给新的对象。这样做可行,但麻烦。
- 可不可以使类具有自行复制本类对象的能力呢?——复制构造函数
定义:复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性,其形参是本类的对象的引用。其作用是使用一个已经存在的对象(由复制构造函数的参数指定),去初始化同类的一个新对象。
注意:
- 复制构造函数又被称为拷贝构造函数。
- 当定义对象时,若希望将已存在的对象的值赋给该对象,则需要调用复制构造函数。
- 在复制构造函数中,通过赋值语句将作为入口参数的对象中的数据成员的值赋给当前对象的对应数据成员中,进而达到对象复制的目的。
- 在定义复制构造函数时,一般是将同一个类的对象引用作为其参数。
// 声明和实现复制构造函数的一般方法:
class 类名
{
public:
类名(形参表); //构造函数
类名(类名 &对象名); //复制构造函数
};
类名::类名(类名 &对象名); //复制构造函数的实现
//(1)复制构造函数的特点是其入口参数为当前类的对象,一般把其说明为对象引用。
//(2)当复制构造函数只有一个参数时,可以采用如下两种方式来调用复制构造函数:
cXydata dt2(dt1);
//或
cXydata dt3=dt1;
/*
其作用都是通过自动调用复制构造函数,
将dt1对象中的数据成员的值赋给新定义的对象(dt2或dt3)中的对应的数据成员。
*/
缺省构造函数
对于一个类来讲,复制构造函数是必须的。如果程序中没有定义复制构造函数,则系统将自动生成一个缺省的复制构造函数,这样,在上面的例子中,即使没有定义复制构造函数,程序也能正常执行。
缺省构造函数工作方式
- 将对象的值原原本本地进行拷贝,这样,如果用户希望在进行对象拷贝的同时附加一些其它功能,或者在被拷贝的对象中包含指针变量,这时,一般来讲就必须要自己在程序中定义相应的复制构造函数了,而不能直接利用由系统定义的缺省复制构造函数了。
👿 以下几种使用场合:
当利用类的已有对象来初始化另一个新对象时,系统将自动调用复制构造函数
void main( ) { xyz a(10,20,30); cout << a.getx( )<< ' '<< a.gety( )<< ' '<< a.getz( )<< '\n'; xyz b(a); //调用复制构造函数 cout << b.getx( )<< ' '<< b.gety( )<< ' '<< b.getz( )<< '\n'; } /* 此程序的执行结果如下: 10 20 30 Entering copy constructor. 10 20 30 */
如果函数的形参是类对象,则在进行函数调用时(将实参传给形参时),将自动调用复制构造函数
void f(xyz m) { cout << "f: "<< m.getx( )<< ' '<< m.gety( )<< ' '<< m.getz( )<< '\n'; } void main( ) { xyz a(1, 2, 3); f(a); } /* 此程序的执行结果如下: Entering copy constructor. f: 1 2 3 */
如果函数的返回值是类对象,则在执行返回语句时自动调用复制构造函数
xyz g( ) { xyz m(7, 8, 9); cout << "g: "<< m.getx( )<< ' '<< m.gety( )<< ' '<< m.getz( )<< '\n'; return m; } void main( ) { xyz n(1, 2, 3); cout << n.getx( )<< ' '<< n.gety( )<< ' '<< n.getz( )<< '\n'; n=g( ); cout << n.getx( )<< ' '<< n.gety( )<< ' '<< n.getz( )<< '\n'; } /* 此程序的执行结果如下: 1 2 3 g: 7 8 9 Entering copy constructor. 7 8 9 */
变换构造函数和变换函数
变换构造函数
只有一个参数的构造函数被称为变换构造函数或转换构造函数。例如:
cXydata::cXydata(int a)
{
x=a; y=0;
}
// ...
// 程序中如果定义了变换构造函数,就可以使用下面的形式来对类的对象进行初始化:
cXydata dt1=100;
/*
它的初始化结果如下:
dt1.x=100
dt1.y=0
这种初始化方式和下面初始化方式完全一样:
*/
cXydata dt1(100);
变换函数
变换函数用于将对象中的一个值返回出来。变换函数采用如下定义形式:
operator 变换的类型( ) { return返回值; }
说明:
程序中定义了某种类型的变换函数之后,就可以使用语句
来调用变换函数。int idt=dt;
如果没有定义变换函数,则上述写法是错误的。
一般来讲,变换函数都是直接从类中返回某一个变量的值,但这不是绝对的,在变换函数中也允许做一些简单的运算,并把运算结果返回出来。
析构函数
构造函数是在定义类对象时被自动调用的成员函数,而析构函数则是在已生成的对象被释放时自动调用的成员函数。析构函数主要用于对已释放的对象进行一些后处理,同构造函数不同,析构函数不是必须的,一般的类都不需要定义析构函数。
- 析构函数的作用:
- 析构函数主要用于对已申请的内存空间进行释放等后处理工作。
- 析构函数有如下一些特征:
- 析构函数的名字是在类名前加上“~”符号。
- 析构函数没有返回值,因此,析构函数没有类型说明。
- 析构函数没有参数。
// ~
👿 何时调用
- 如果在一个函数中定义了一个对象(假没是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数
- 静态(static)局部对象在函数调用结来时对象并不释放,因此也不调用析构函数、只在main函数结束或调用exit函数结束程序时,才调用static局部对象析构函数
- 如果定义了一个全局对象,则在程序的流程离开其位用域时(main函数结束来或调用exit函数)时,调用该全局对象的析构函数
- 如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数
作用:主要用于对已申请的内存空间进行释放等后处理工作
调用构造函数和析构函数的顺序
调用析构函数的次序与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。
先构造的后析构,后构造的先析构。
相当于一个栈,先进后出。
对象数组
对象数组的每一个元素都是同类的对象。
// 语法
// 定义
类名 数组名[常量表达式];
/* 例如
arrdef a[10]; // a是具有10个元素的arrdef类型的对象数组。
Student Stud[3] = { // 定义对象数组
Student(1001,18.87), // 调用构造函数初始化
Student(1002,12.87),
Student(1003,18.72),
}
*/
// 访问
数组名[下标表达式].成员名
/* 例如
a[0].setx(10); //调用对象a[0]的setx函数
a[6].disp( ); //调用对象a[6]的disp函数
*/
对象指针
对象空间的起始地址就是对象的指针。可以定义一个指针变量,用来存放对象的地址,这就是指向对象的指针变量。
指向对象的指针变量----对象有地址,存放对象的起始地址的指针变量。
指向对象成员的指针变量----对象中的成员(数据成员和成员函数)也有地址,存放对象成员地址的指针变量。
// 语法
// 向对象数据成员的指针
类名 * 对象指针名
/*
指向对象成员函数的指针
指向普通函数的指针变量定义形式:
*/
类型名 (*指针变量名)(参数列表);
void (*p)();//p是指向void型函数的指针变量
p=fun; //将fun函数的入口地址赋给指针变量p,p就指向fun
(*p)( ); //调用fun函数
/*
指向公有成员函数的指针变量的定义形式:
*/
数据类型名 (类名::*指针变量名)(参数列表);
void (Time::*pt)( );//定义pt为指向Time类中公有成员函数的指针变量
pt=&Time::get_time;//使指针变量指向公有成员函数get_time
this指针
- 所谓this指针,就是指向当前对象的指针, 是当前被调用的成员函数所在的对象的起始地址。
- 成员函数无论是内部定义还是外部定义,系统都自动将指向当前对象的this指针传递给成员函数。
共用数据的保护——常对象
C++虽采取不少措施来增加数据的安全性,如private,但有些数据往往是共享的,例如实参和形参、变量及其引用、数据及其指针等,人们可以在不同的场合通过不同的途径访问同一个数据对象。有时无意中的误操作会改变有关数据的状况,如何即能使数据在一定范围内共享,又要保证它不被任意修改,可以用const
定义成常量。
常对象
定义对象时加关键字const
,指定对象为常对象。
- 常对象必须要有初值
// 语法
//
类名 const 对象名[(实参表)];
// or
const 类名 对象名[(实参表)];
// 例
const fraction dt1(1, 4); // 它表示dt1对象被初始化以后将不能再改变了。
定义常对象成员
- 常对象成员:
- 只能通过构造函数的参数初始化表对常数据成员进行初始化,任何其他函数都不能对常数据成员赋值。
- 构造函数只能用参数初始化表对常数据成员进行初始化。
// 语法
const int hour; // 定义hour为常数据成员
- 常成员函数
- 如果将成员函数声明为常成员函数,则只能引用本类中的数据成员,而不能修改。
// 语法
// 一般形式
// 类型名 函数名(参数表)const;
void get_time()const; // 注意const的位置在函数名和括号之后
指向对象的常指针
将指针变量声明为const型,指针变量始终保持为初值,不能改变,即其指向不变。(指针变量始终指向一个对象,一般用于函数形参)
/*
定义指向对象的常指针变量的一般形式:
类名 * const 指针变量名;
*/
Time * const pt; //const位置在指针变量名前面,pt是常指针变量. ptrl指向tl
指向常对象的指针变量
/*
定义指向常对象的指针变量的一般形式:
const 类名 * 指针变量名;
*/
// 例如:
Time * const pt; //指向对象的常指针变量----指针不变
const Time * pt; //指向常对象的指针变量----指针指向的对象不变
注意:
- 如果一个对象已经被声明为常对象,只能用指向常对象的指针变量指向它,而不能用一般的指针变量指向它。
- 如果定义了一个指向常对象的指针变量,并使它指向一个非const的对象,则其指向的对象是不能通过该指针变量来改变的。
- 指向常对象的指针最常用于函数的形参,目的是在保护形参指针所指向的对象,使它在函数执行过程中不被修改。
- 如果定义了一个指向常对象的指针变量,是不能通过它改变所指向的对象的值的,但是指针变量本身的值是可以改变的。
不同对象间实现数据共享----静态成员
静态成员的定义方法: (1)在类中的数据成员和成员函数的前面加上static关键字即可
(2)静态数据成员和静态成员函数并不是每生成一个对象就产生其一个拷贝,而是所有对象共用一个。
(3)静态数据成员主要用于设定对所有对象都共享的数据。
(4)设置静态成员函数的目的是为了提高内存的利用效率,因为对所有对象都共享一份代码。
(5)由于不将this指针传递给静态成员函数,所以静态成员函数不能对一般的数据成员进行操作,通常,它只用于对静态数据成员进行处理。
// 在类的定义中,下面的写法用于说明一个静态数据成员ct和静态成员函数count( ):
class cVdata
{
static int ct;
static int count( ){……}
// ……
};
注意:
对于静态数据成员可以利用如下两种方式来进行访问:
cVdata::ct=100; //利用类名访问 dt1.ct=100; //利用对象来进行访问
静态成员函数只能直接访问该类的静态数据成员和静态成员函数。若要访问非静态数据成员则必须通过对象来访问
class A { public: static void f(A a); private: int x; }; // 测试 void A::f(A a) { cout << x; //出错 cout << a.x; //正确 }
需要注意的是,构造函数和析构函数不能定义为静态的。
友元函数和友元类
友元函数
在类中函数说明的前面加上friend 关键字,就将该函数说明为友元函数。
// 声明display函数为Time类的友元函数
friend void display(Time &);
注意:
- 在类中,函数说明语句的前面加上friend就表示该函数为此类的友元函数。友元函数可以直接引用类中的private变量。
- 友元函数的说明可以出现在类中的任何位置上(私有、受保护的或公共部分),其作用都是一样的。
- 需要注意的是,**友元函数不是类中的成员函数,任何一个不是类的成员函数的函数都可以成为该类的友元函数。**友元函数的定义也可以在类中直接给出,但这并不意味着该友元函数就是类的成员函数,在类中定义和在类外定义的友元函数都是一样的。
友元类
不但函数可以作为一个类的友元,同时一个类也可以作为另一个类的友元。当一个类作为另一个类的友元时,该类中的所有成员函数都可以对另一个类中的数据成员(包括private成员)进行访问。
// 语法
friend 类名;
注意:
- 友元的关系是单向的不是双向的。
- 友元的关系不能传递,如果B类是A类的友元类,C类是B类的友元类,不等于C类是A类的友元类
模板和异常处理
模板
解决问题:在实际程序设计过程中,往往会发现这样的现象:程序中所定义的两个或多个函数的函数体完全一样,所不同的只是它们的参数类型不一样。
在C++语言中,对这样的两个函数可以先给出其通用的框架定义,而将其参数类型作为可变化的部分来进行处理,当需要调用这样的函数时,再将具体的参数及其类型传送给它,这就是模板的概念。
定义:所谓模板,就是首先定义作为原型的函数或类的框架,然后在需要时再根据已定义的原型来生成具体的函数或类。
模板的类型
函数模板
/* template <模板参数表> 函数值类型 函数名 (参数表) { 函数体 } */
类模板
/* template <模板参数表> class 类名 { 数据成员 成员函数 }; */
注意:
声明类模板时,增加一行:
template<class 虚拟类型参数名>
类模板外定义成员函数:
template<class 虚拟类型参数名> 函数类型 类模板名<虚拟类型参数>::成员函数名(参数列表){…}
用类模板定义对象:
类模板名<实际类型名>对象名(参数表);
异常处理
异常处理又被称为例外处理。所谓异常处理是指出现非正常情况时的处理,绝大部分情况是指错误处理。
/*
由try关键字括起来的部分主要用于检查是否出现错误。其用法如下:
注意:没有用try关键字括起来的部分不作为错误检查的部分。
*/
try {
//进行错误检查的程序部分
//示例
if ( 出错1 )throw “Error1”; //字符串
if ( 出错2 )throw “File open error !”;
if ( 出错3 )throw 1; //可以是错误号
/*
throw关键字的使用
当由try所括起来的程序段中检测出错误信息时,在程序中并不是立即进行处理,而是产生一个用于表示发生某种错误的字符串或数值,此字符串或数值通过throw关键字传递出去,以便在下面介绍的catch段中进行处理。
*/
}
catch(){
/*
catch关键字的使用
在try程序段中由throw关键字抛出的错误信息,将由catch关键字段捕获。这里的catch实际上相当于错误处理器,catch将根据由throw抛出的信息类型,来分别进行相应的处理。
如果需要的话,可以根据错误信息的数据类型来使用多个catch语句,如果throw只抛出一种错误信息类型,则只要准备一个catch语句即可。catch语句是紧接在try语句的后面来使用的。
*/
}
运算符重载
所谓运算符重载,就是对语言中已有的运算符重新进行定义,赋予其另一种功能。运算符的重载一般是在给定的类中实现的,这样,就可以在该类中使用已重载的运算符来进行相应的操作。
// 语法
函数类型 operator 运算符名称 (形参表){
// 对运算符的重载处理
}
重载规则
不能重载的运算符
不能重载的运算符 含义 .
成员访问运算符 *
成员指针访问运算符 ::
域运算符 sizeof
长度运算符 ?:
条件运算符 允许重载的运算符
允许重载的运算符 具体 双目运算符 +
(加)、-
(减)、*
(乘)、/
(除)、%
(取模)关系运算符 ==
(等于)、!=
(不等于)、<
(小于)、>
(大于)、<=
(小于或等于)、>=
(大于或等于)逻辑运算符 ` 单目运算符 +
(正)、-
(负)、*
(指针)、&
(取地址)自增自减运算符 ++
(自增)、--
(自减)位运算符 ` 赋值运算符 =
、+=
、-=
、*=
、/=
、%=
、&=
、`空间申请运算符 new
、delete
、new[]
、delete[]
其他运算符 ()
(函数调用)、->
(成员访问)、->*
(成员指正访问)、,
、[]
(下标)
👿定义:
运算符重载是对语言中已有的运算符重新进行定义赋予另一种功能当定义一个新的能够作用于他的操作符,例如实现复数相加时。
// 定义complex类中,定义实部与虚部,进行声明 "+"的运算符重载,
complex operator+(Complex &c2);
// 在类外定义
complex operator+(Complex &c2){
complex c;
c.real = real + c2.real;
c.img = imag + c2.imag;
return c;
}
// 数中用于复数运算
c3 = c1 + c2;
// 若不进行 运算符重载则咋编译器无法识别
类的继承
继承
继承在C++语言中,通过使用已有的类并在此基础上追加新的功能就可以派生出新的类,这一处理过程被称为继承。
基类和派生
被继承的类称为基类,通过继承而产生的新类被称为派生类或导出类。通常基类又被称为父类,派生类又被称为子类。派生类继承了基类的功能。
// 语法
class 派生类名: public 基类名
{
追加的数据成员
追加的成员函数
};
/*
位于冒号后面的public,表示当派生类继承了基类以后,在派生类中对基类成员的访问控制,即继承方式。
*/
对基类的访问控制
对基类的访问控制与继承方式有关,在C++语言中共有3种继承方式:
public
(公有继承)protected
(保护继承)private
(私有继承)当对基类的访问控制为public时
基类 派生类 public 成员 public 处理 protected 成员 protected 处理 private 成员 不可访问 当对基类的访问控制为protected时
基类 派生类 public 成员 protected 处理 protected 成员 protected 处理 private 成员 不可访问 当对基类的访问控制为private时
基类 派生类 public 成员 private 处理 protected 成员 private 处理 private 成员 不可访问
在派生类的构造函数中可以使用基类的构造函数
通过继承操作所生成的派生类可以有自己的构造函数,为了能够在派生类的构造函数中为基类中的数据成员进行初始化,可以在派生类的构造函数中来调用基类中的构造函数。
派生类构造函数的一般形式:
// 语法
派生类构造函数名(总参数表) : 基类构造函数名(基类参数表)
{
派生类中新增数据成员初始化语句;
}
建立一个对象时,执行构造函数的顺序:
- 派生类构造函数先调用基类构造函数;
- 再执行派生类构造函数本身(派生类构造函数函数体)
有子对象的派生类的构造函数
// 语法
派生类构造函数名(总参数表) : 基类构造函数名(基类参数表),子对象名(参数表)
{
派生类中新增数据成员初始化语句;
}
在建立一个对象时,执行有子对象的派生类构造函数的顺序
- 调用基类构造函数,对基类数据成员初始化;
- 调用子对象构造函数,对子对象数据成员初始化;
- 再执行派生类构造函数本身,对派生类数据成员初始化。
派生类构造函数的特殊形式:
从上面的程序段可以看出,在派生类中可以使用能够被派生类所继承的基类中的所有函数,包括基类中的public成员和protected成员。
多重继承
定义
class cMids: public cMint, public cMdbl
{
……
};
/*
它表示通过对cMint和cMdbl类进行public继承之后,生成了派生类cMids。每个基类之间通过逗号隔开,每个基类名的前面都需要加上public、protected或private说明,如果不写的话,其缺省值为private。
*/
特点:
在派生类中调用多个基类的构造函数
cMids::cMids(int d1,double d2,char *str):cMint(d1), cMdbl(d2) { strcpy(ss,str); }
在派生类的成员函数中,若需要可使用作用域限定运算符来调用基类中的成员函数。
在一般函数中若需要的话,也可以使用作用域限定运算符
在一般函数中利用派生类对象来调用基类中的成员函数时,需要注意对基类的访问控制设置。
继承的种类
直接继承和间接继承
派生类是通过继承某个(或某些)基类而产生的,同时派生类本身也可以作为另外一个派生类的基类,这样就形成了一种多层次的继承关系。
虚函数和多态
多态
一般来讲,多态是指同一种东西有多种形态。在C++语言中,所谓多态就是指一个名字代表多种具体的对象。 在C++语言中,多态是通过虚函数来实现的。多态的概念比较抽象,单纯用文字来叙述的话,难理解。我们将通过易于理解的例子来介绍多态的实现和使用。
虚函数
前面已经介绍过,只有采用动态结合方式才能实现多态性。在C++语言中,虚函数是向系统表明采用动态结合方式的函数,它是通过在一般成员函数的前面加上virtual关键字来定义的。定义了虚函数以后,就可以实现程序的多态性。
虚函数的使用方法:
- 在基类中用
virtual
声明成员函数为虚函数。类外定义虚函数时,不必在函数名前加virtual
- 在派生类中重新定义此函数,函数名、函数类型、函数参数个数和类型必须与基类的虚函数相同,根据派生类的需要重新定义函数体。
- 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
- 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
纯虚函数与抽象类
纯虚函数
纯虚函数是在声明虚函数时被“初始化”为0的函数。声明纯虚函数的一般形式是
virtual 函数类型 函数名 (参数列表) = 0;
纯虚函数没有函数体
最后面的 =0
,并不表示函数返回值为0,它只告诉编译系统“我是是纯虚函数”
纯虚函数只有函数的名字而不具备函数的功能,不能被调用
抽象类
包含纯虚函数的类被称为抽象类,也就是在抽象类中包含没有函数体定义的函数。以抽象类作为基类来生成派生类时,必须要在派生类中给出纯虚函数的定义,如果在派生类中没有给出纯虚函数的定义,则该派生类也自动成为抽象类。需要注意的是,抽象类是不能定义其对象的。
示例程序
/*
1.编写程序实现给职工加工资功能,
若其工资大于5000,则加500元,
若在3000--5000之间,则加800元,
若3000元以下,则加1000元。
*/
#include <iostream>
using namespace std;
int main()
{
int i;
cout<<"请输入工资"<<endl;
cin>>i;
if(i>5000)
i+=500;
else if(3000<=i<=5000)
i+=800;
else if(i<3000)
i+=1000;
cout<<"你的工资是:"<<i<<endl;
return 0;
}
/*
2.建立一个对象数组,内放5个学生的数据(学号、成绩),
用指针指向数组首元素,
输出第1,3,5个学生的数据。
*/
#include <iostream>
using namespace std;
class Student{
public:
Student(int n,float s):num(n),score(s){}
void display();
private:
int num;
float score;
};
void Student::display(){
count<<num>>""<<score<<endl;
}
int main(){
Student stud[5] = {
Student(101,78.5),
Student(102,85.5),
Student(103,98.5),
Student(104,100.0),
Student(105,95.5)
}
Student *p = stud;
for(int i = 0;i<=2;p=p+2,i++){
p -> display();
}
return 0;
}
实验四相关
/*
9.7 用不同的方法输出输出时间记录器的时、分、秒,
注意对象指针的使用方法
*/
#include <iostream>
using namespace std;
class Time{
public:
Time(int,int,int);
int hour;
int minute;
int sec;
void get_time();
};
Time::Time(int h,int m,int s){
hour = h;
minute = m;
sec = s;
}
void Time::get_time(){ // 打印时分秒
cout<<hour<<":"<<minute<<":"<<sec<<endl;
}
int main(){
Time tl(10,13,56); // 构造函数赋初值
int *pl = &tl.hour;
cout<<*pl<<endl; // 打印p1所指向的数据成员tl.hour
tl.get_time(); // 调用get_time
Time *p2 = &tl;
p2->get_time(); // p2 指向 t1的get_time
void(Time::*p3)(); // 定义指向Time类公用成员函数的指针变量 p3
p3 = &Time::get_time; // p3 指向Time类中的 get_time
(tl.*p3)(); // 调用t1.p3 (t1.get_time)
}
/*
9.11 统计学生平时成绩,使用静态成员函数
*/
#include <iostream>
using namespace std;
class Student{
public:
Student(int n,int a,float s):num(n),age(a),score(s){}
void total();
static float average();
private:
int num;
int age;
float score;
static float sum;
static int count;
};
void Student::total(){
sum+=score;
count++;
}
float Student::average(){
return (sum/count);
}
float Student::sum = 0;
int Student::count = 0;
int main(){
Student stud[3] = {
Student(1001,18,70),
Student(1002,19,78),
Student(1005,20,98)
};
int n;
cout<<"pleace input the number of student";
cin>>n;
for(int i=0;i<n;i++){
stud[i].total();
}
cout<<"the average score of"<<n<<"student is"<<Student::average()<<endl;
return 0;
}
/*
9.13 有一个日期(Date)类的对象和一个时间(Time)类的对象
均已指定了内容,要求一次输出其中的日期和时间
*/
#include <iostream>
using namespace std;
class Date;
class Time{
public:
Time(int,int,int);
void display(Date &);
private:
int hour;
int minute;
int sec;
};
class Date{
public:
Date(int,int,int);
friend void Time::display(Date &); // 声明Time中的display函数为本类的友元成员函数
private:
int month;
int day;
int year;
};
Time::Time(int h,int m,int s){
hour = h;
minute = m;
sec = s;
}
void Time::display(Date &d){ // 输出年月日 和 时分秒
cout<<d.month<<"/"<<d.day<<"/"<<d.year<<"/"<<endl;
cout<<hour<<"/"<<minute<<"/"<<sec<<endl;
}
Date::Date(int m,int d,int y){
month=m;
day = d;
year = y;
}
int main(){
Time tl(10,13,56);
Date dl(12,25,2004);
tl.display(dl);
return 0;
}
/*
习题四 建立一个对象数组,内放有5个学生的数据(学号、成绩),
用指针指向数组首元素,输出第1、3、5学生的数据
*/
#include<iostream>
using namespace std;
class Student{
public:
Student(int n,double s):num(n),score(s){}
void display();
private:
int num;
double score;
};
void Student::display() {
cout<<"student:"<<num<<", score:"<<score<<endl;
}
int main(){
Student stu[5]={
Student(1001,85),
Student(1002,97),
Student(1003,78),
Student(1004,91),
Student(1005,88.5)
};
Student *p;
p=&stu[0]; // 1
(*p).display();
p=p+2; // 3
(*p).display();
p=p+2; // 5
(*p).display();
return 0;
}
/*
习题 5 建立一个对象数组,内放有5个学生的数据(学号、成绩),
设立一个函数max,用指向对象的指针做函数参数,在max函数中找出5个
学生中成绩最高者并输出其学号.
*/
#include<iostream>
using namespace std;
class Student{
public:
Student(int n,double s):num(n),score(s){}
void display();
double sc();
private:
int num;
double score;
};
void Student::display() {
cout<<"student:"<<num<<", score:"<<score<<endl;
}
double Student::sc(){
return score;
}
void max(Student *p){
double max;
max=(*p).sc(); //用一个公有成员函数得到score
for(int i=0;i<5;i++){
if((*p).sc()>max) max=(*p).sc();
p++;
}
cout<<"max="<<max;
}
int main(){
Student stu[5]={
Student(1001,85),
Student(1002,97),
Student(1003,78),
Student(1004,91),
Student(1005,88.5)
};
Student *p;
p=&stu[0];
max(p);
return 0;
}
/*
习题 9 商店小时某一商品,每天公布统一的折扣(discount)。
同时允许销售人员在销售时灵活掌握售价(prece),在此基础上
一次购买10件以上,还可以享受9.8折优惠,
*/
#include<iostream>
using namespace std;
class Sale{
public:
Sale(int n,int q,double p){
num=n;
quantity=q;
price=p;
}
void sumsale(){ //公有成员函数既可以访问静态成员也可以访问非静态成员
if(quantity<=10){
sum=quantity*price*discount;
}
else{
sum=quantity*price*0.98*discount;
}
n=quantity;
}
static double average(){ //静态成员函数只访问静态数据成员不可访问非静态成员,(要访问必须限定对象,那么一定要将对象定义放在前面)
return sum/n;
}
static void display(){
cout<<"average:"<<average()<<endl;
}
private:
int num;
int quantity;
double price;
static double sum;
static int n;
static double discount;
};
double Sale::discount=0.9;//静态成员只能在类外初始化,属于类
double Sale::sum=0.0;
int Sale::n=0;
int main(){
Sale s1(101,5,23.5);
Sale s2(102,12,24.56);
Sale s3(103,100,21.5);
s1.sumsale();
cout<<"No 1: ";
s1.display();
s2.sumsale();
cout<<"No 2: ";
s2.display();
s3.sumsale();
cout<<"No 3: ";
s3.display();
return 0;
}