C++笔记

257 阅读27分钟

第二章 数据的存储、表示形式和基本运算

2.1 C++的数据类型

  • 基本类型

    • 整型
      • 短整型short (2)
      • 整型 int (4)
      • 长整型 long (8)
      • 长长整型 long long (8)
    • 字符型 char (1)
    • 浮点型
      • 单精度型
      • 双精度型
      • 长双精度型
    • 布尔型 bool
  • 派生类型

    • 指针类型
    • 枚举类型
    • 数组类型
    • 结构体类型
    • 共用体类型
    • 类类型
  • 空类型

    2.2 常量

    • 数值型常量
    • 字符型常量

    标识符:用来标识变量、符号常量、函数、数组、类型等实体名字的有效字符序列。

    变量初始化:允许在定义变量时对它赋予一个初值(常量或者表达式)

    常变量:const修饰的变量,变量的值不允许改变

    2.4 C++的运算符

    • 算术运算符(加减乘除自增自减正号减号取模)
    • 关系运算符(大于小于等于)
    • 逻辑运算符
    • 位运算符
    • 赋值运算符
    • 条件运算符
    • 逗号运算符
    • 指针运算符
    • 取址运算符
    • 求字节数运算符
    • 强制类型转换运算符
    • 成员运算符
    • 指向成员的运算符
    • 下标运算符

    char 、short --> int -->unsigned-->long-->double

第3章 程序设计初步

  • C++即可以用来进行基于过程的程序设计,又可以用来进行面向对象的程序设计;
  • 基于过程的程序设计(Procedure-Based Programming)又称为过程化的程序设计;
  • 在20世纪90年代之前,几乎所有的计算机语言(BASIC,FORTRAN, COBOL, Pascal, C)都是过程化的语言;

3.1 基于过程的程序设计和算法

基于过程的程序设计反映的是事物在计算机中的实现方式,而不是事物在现实生活中的实现方式

3.1.1 算法的概念

一个基于过程的程序应该包括:

  1. 对数据的描述——在程序中要指定数据的类型和数据的组织形式,即数据结构;
  2. 对操作的描述——即操作步骤,也就是算法
程序=算法+数据结构程序 = 算法 + 数据结构

算法是处理问题的一系列的步骤,算法必须具体地指出在执行时 每一步应当怎么做。

计算机算法的类别:

  • 数值算法
  • 非数值算法
    • 图书检索
    • 人事管理
    • 行车调度管理

3.1.2 算法的表示

  1. 自然语言 用中文或英文等自然语言描述算法;

  2. 流程图 形象直观,但不易修改,适合给别人讲解算法;

  3. 伪代码 介于自然语言和计算机语言之间的文字和符号来描述算法,比如

    If x is positive then
    	print x
    else
    	print -x
    
  4. 用计算机语言表法算法

3.2 C++的程序和C++语句

一个程序包含一个或多个程序单位(每个程序单位构成一个程序文件)

第一个程序单位由以下3个部分组成:

  • 预处理指令。如 #include 和 #define 指令
  • 全局声明。在函数外部对数据类型、函数以及变量的声明和定义(也称为全局变量)
  • 函数。包括函数首部和函数体,每个函数实现一个或多个功能,函数体包含声明语句和执行语句
#include<iostream>			//预处理命令
using namespace std;		//在函数外的全局声明
int a = 3;					//在函数外的全局声明(全局变量)
int main()					//函数首部
{							//函数开始
    float b;				//声明语句
    b = 4.5;				//执行语句
    cout<< a << b;			//执行语句
    return 0;				//执行语句
}							//函数结束

**全局变量:**在函数外部声明的变量。

C++语句可以分为以下4种:

  1. 声明语句

    int a, b;
    
  2. 执行语句

    • 控制语句

      • if( ) else
      • for( )
      • while( )
      • do~while( )
      • continue
      • break
      • switch
      • goto
      • return
    • 函数和流对象调用语句

      int maxNum = max(a, b);		//调用max()函数语句
      cout<< maxNum << endl;		//输出流调用对象语句
      
    • 表达式语句

      i = i + 1		//一个赋值表达式,末尾没有分号
      j = j + 2;		//一个赋值语句,末尾有分号
      

      任何一个表达式的最后加一个分号都可以成为一个语句

    • 空语句 ;

    • 复合语句 使用{ }将一些语句括起来成为复合语句

3.3 赋值操作

1、C++的赋值语句具有其他高级语言的赋值语句的功能,但不同的是:C++中的赋值号“=”是一个赋值运算符,可以写成:

a = b = c = d;

该语句从右往左读,将变量d赋值给变量c,再把变量c的值赋值给b,最后赋值给a

2、关于赋值表达式和赋值语句的概念。其他高级语言没有“赋值表达式这一概念,在C++中,赋值表达式可以包括在其他表达式中:

if((a = b) > 0)				//先把变量b的值赋给a,再比较 a 是否大于 0 
{
    cout<<"a > 0"<<endl;
}

3.4 C++的输入与输出

  • 在C语言中,输入和输出的功能是通过调用scanf( )函数和 printf 函数来实现的;
  • 在C++中,是通过调用输入输出库中的流对象cin 和 cout 实现的
  • 输入输出不是由 C++本身定义的,而是编译系统提供的 I/O 库中定义的;

3.4.1 输入流和输出流的基本操作

cout 语句的一般格式:

cout<<表达式 1<< 表达式 2<<…<< 表达式 n;

cin语句的一般格式:

cin >> 变量 1 >> 变量 2
  • 在定义流对象时,系统会在内存中开辟一段缓冲区,用来暂存输入输出流的数据;
  • 在用 cout 输出时,系统会自动判别输出数据的类型,使输出的数据按相应的类型输出;

3.4.2 在 IO 流中使用控制符

控制符作用
dec设置数值的基数为 10
hex设置数值的基数为 16
oct设置数值的基数为 8
setfill ( c )设置填字符 c,c 可以是字符常量或字符变量
setprecision(n)设置浮点数的精度为 n 位。在一般十进制小数形式输出时,n 代表有效数。在以 fixed(固定小数位数)形式和 scienfific(指数)形式输出时,n 为小数位数
setw(n)设置字段宽度为 n 位
setiosflags(ios::fixed)设置浮点数以固定的小数位数显示
setiosflags(ios::scientific)设置浮点数以科学计数法显示
setiosflags(ios::left)左对齐
setiosflags(ios::right)右对齐
setiosflags(ios::skipws)忽略前导的空格
setiosflags(ios::uppercase)数据以十六进制形输出时字母以大写表示
setiosflags(ios::lowercase)数据以十六进制形输出时字母以小写表示
setiosflags(ios::showpos)输出正数时加上"+"号
  • 如果使用了控制符,则需要加上 iomanip 头文件
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
    double a = 123.456789012345;
    
    cout << a << endl;//123.457
    cout << setprecision(9) << a << endl;//123.456789
    cout << setprecision(6) << a << endl;//123.457
    cout << setiosflags(ios::fixed) << a << endl;//123.456789
    cout << setiosflags(ios::fixed) << setprecision(8) << a << endl;//123.45678901
    cout << setiosflags(ios::scientific) << a << endl;//0x1.edd3c07fb4c69p+6
    return 0;
}

3.4.3 用 getchar 和 putchar 函数进字符的输入和输出

  1. putchar 函数(字符输出函数) 作用:向终端输出一个字符 语法:putchar(c)
  2. getchar 函数(字符输入函数) 作用:从终端输入一个字符 语法:getchar( );

3.4.4 用 scanf 和 printf 函数进输入和输出

  1. scanf 函数的格式

    scanf(格式控制,输出表列)
    printf(格式控制,输出表列)
    
#include <iostream>
using namespace std;

int main()
{
    int a;
    float b;
    char c;
    scanf("%d %c %f", &a, &c, &b);
    printf("a = %d, b= %f, c = %c\n", a, b, c);
    
    return 0;
}

3.5 编写顺序结构的程序

从上到下顺序执行各语句,这就是顺序结构的程序

3.6 关系运算和逻辑运算

3.6.1 关系运算和关系表达式

关系运算:形如 amount < 1000 这样的比较运算

关系表达式:amount < 1000,用关系运算符将两个表达式连接起来的式子

表达式 关系运算符 表达式

常用运算符的优先级:

算术运算符 > 关系运算符 > 赋值运算符

表达式可以是以下几种:

表达式例子
算术表达式a+b > b+c
关系表达式(a>b) > (b<c)
逻辑表达式flag == (a>b) && (x>y)
赋值表达式d = a > b(d要么是0要么是1)
字符表达式'a' > 'b'

C++的关系运算符有:

  • 优先级高的4个运算符:

    • < 小于
    • <=
    • 大于
    • 大于等于
  • 优先级低的2个运算符:

    • ==
    • !=

3.6.2 逻辑常量和逻辑变量

//最后a的值为多少?
int a = 0;
bool flag = true;
a = a + flag + true;//此处其实进行了隐式类型转换

3.6.3 逻辑运算和逻辑表达式

C++提供3种逻辑运算符:

  1. && 逻辑与
  2. || 逻辑或
  3. ! 逻辑非

逻辑表达式:

x > 0 && x <= 100;

表达式 逻辑运算符 表达式

优先级顺序:

! > 算术运算符 > 关系运算符 > &&和|| > 赋值运算符

a > b && x > y;//(a>b) && (x>y)
a==b || x==y;//(a==b) || (x==y)
!a || a>b;//(!a) || (a>b)

表达式的类型:

  • 关系表达式
  • 整型、字符型、浮点型或指针型数据等

计算某一年份是否是闰年:

while((year%4 == 0 && year%100 !=0)||year%400 == 0)
{
    cout<<"leap year"<<endl;
}

3.7 选择结构和if语句

3.7.1 if 语句的形式

  1. 单 if 语句

    if (表达式1)	//表达式一般为关系表达式或逻辑表达式
    {
        语句1;
        语句2;
    }
    
  2. if ... else

    if (表达式1)
    {
        语句1;
        语句2;
    }
    else
    {
    	语句3;
    }
    
  3. if ... else if ... else if ... else

    if (表达式1)
    {
        语句1;
        语句2;
    }
    else if (表达式2)
    {
        语句3;
    }
    else
    {
        语句4;
    }
    

3.7.2 if 语句的嵌套

一个if 语句包含多个if 语句的形式称为if 语句的嵌套

3.7.3 条件运算符和条件表达式

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

该段代码等效于:

max = (a > b) ? a : b;

以上? :称为条件运算符,或者称为三目运算符

形式:表达式1 ? 表达式2 : 表达式3

此类运算符的使用范围,非真即假的时候,即二极管思维,通路或者不通

3.7.4 多分支选择语句与switch语句

如果分支较多,则嵌套的if 语句就会显得很冗长而且可读性降低,此时一般使用switch语句直接处理多分支语句

switch(表达式)
{
    case 常量表达式1:语句1;break;
    case 常量表达式2:语句2;break;
    case 常量表达式3:语句3;break;
    ......
    default:语句n+1;break;
}

3.8 循环结构和循环语句

3.8.1 用 while 语句构成循环

一般形式:

while(表达式)
{
    语句1;
}

3.8.2 用 do-while 语句构成循环

特点:先执行一次循环体,然后再判断循环条件是否成立

一般形式:

do
{
    语句1;
    i++;
}while(表达式);

两者的区别:

do-while 无论是否满足循环条件,都会执行一次循环体

3.8.3 用 for 语句构成循环

一般格式:

for (循环变量赋初值;循环条件;循环变量增值)
{
    语句1;
}

3.8.4 循环的嵌套

3.8.5 提前结束循环

  • 结束本次循环——continue
  • 结束循环——break

3.8.5 编写循写结构的程序

第4章 利用函数实现指定的功能

4.1 什么是函数

4.1.1 为什么需要函数

  • 人们往往编写一些函数,用来实现各种功能;
  • 解题的过程就是编写函数和调用和执行一系列函数的过程;
  • 一个函数就是一个功能;

4.1.2 函数调用举例

  • 函数不能嵌套定义
  • 各函数之间可以互相调用,但是不能调用main()函数
  • 如果自定义函数不在主函数声明,则位置必须在主函数之前

4.1.3 函数的分类

  • 系统函数,即库函数 这是由编译系统提供的,如sin函数,库函数无需声明,因为头文件已经包含
  • 用户自定义函数
    • 无参函数
    • 有参函数

4.2 定义函数的一般形式

4.2.1 定义无参函数的一般形式

类型名 函数名()
{
    声明部分;
    执行语句;
}

4.2.2 定义有参函数的一般形式

类型名 函数名()
{
    声明部分;
    执行语句;
}
int max(int x, int y)
{
    int z;
    z = x > y ? x:y;
    return z;
}

4.3 函数参数和函数的值

4.3.1 形式参数和实际参数

  • 形参:函数定义时括号内的参数;
  • 实参:函数调用时括号内的实际值;

有关形参和实参的说明:

  • 在定义函数时指定的形参,在未发生函数调用时,它们并不占用内存空间,只有在发生函数调用时,函数形参才会分配内存空间。调用结束后,内存空间被释回;
  • 实参可以是常量,变量或者表达式
  • 定义函数形参时,必须指定形参的数据类型
  • 实参变量对形参变量的数据传递是“值传递”,即单向传递,只由实参传递给形参,而不能由形参传给实参

4.3.2 函数的返回值

返回值:函数的调用使主调函数能得到一个确定的函数值

  • 函数的返回值通过 return 语句获得;
  • 一个函数可以有一个或多个 return 语句;
  • 定义函数时,如果有返回值要指定返回值的类型;
  • 如果函数值的类型和 return 语句中表达式的值不一致,以函数类型为准,对数值型数据,可以自动进行类型转换;

4.4 函数的调用

4.4.1 函数调用的一般形式

函数名([实参列表])

max(i,++i);
//如果i=3,那么是max(3,3)还是max(4,3)
//一般编译器都是自右向左编译的,所以是max(4,3)

4.4.2 函数调用的方式

  • 函数语句

    printstar(30);
    
  • 函数表达式

    c = 2 * max(a, b);
    
  • 函数参数

    m = max(a, sqrt(b));//sqrt(b)作为max函数调用的一个实参
    

4.4.3 对被调用函数的声明和函数原型

在一个函数中调用另一个函数需要具备以下条件:

  • 被调用的函数是存在的函数;
  • 如果使用库函数,必须包含相应的头文件;
  • 如果使用用户已定义的函数,必须在main()函数之前或者进行在函数开始处进行函数声明;

在函数声明中也可不写形参名,此时称为函数原型

int max(int, int);//函数原型的函数声明

那为什么不把所有的被调用函数都写在main()函数之前呢?

1、这对程序员提出较高的要求,在复杂的程序中,必须周密考虑和正确安排各函数的顺序;

2、阅读程序的人需要十分耐心地逐一仔细阅读每一个被调用函数,直到最后才看到主函数,程序可读性较差;

3、有经验的程序员一般把 main函数写在最前面,然后用函数原型来声明函数

4、函数声明的位置可以在被调用函数所在的函数中,也可以在函数之外。如果放在外部,则是对函数的外部声明,作用域是整个文件

4.5 函数的嵌套调用

4.6 函数的递归调用

在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用

程序中不能出现无终止的递归调用,只应出现有限次数的、有终止的递归调用;

4.7 内置函数

调用函数时需要一定的时间和空间的开销,下面是函数调用的过程:

  1. 程序先执行被调用函数之前的语句;
  2. 流程的控制转移到被调用函数的入口处,同时进行参数传递;
  3. 执行被调用函数中的函数体语句;
  4. 流程返回调用函数的下一条指令处,将函数返回值带回;
  5. 接着执行主调用函数中未执行的语句;

C++提供一种提高效率的方法,即在编译时将所调用函数的代码直接嵌入到主调用函数中,而不是将流程转出去。

这样嵌入到主调用函数中的函数被称为内置函数(内联函数)

inline int max(int, int, int);	//声明内置函数
int main()
{
	int i = 10, j = 20, k = 30, m;
	m = max(i, j, k);
	cout << m << endl;
	return 0;
}
inline int max(int a, int b, int c)//此处inline可以省略
{
	if (b > a)
		a = b;
	if (c > a)
		a = c;
	return a;
}

注意事项:

  • 一般只将5个语句以下而使用频繁的函数声明为内置函数
  • 内置函数不能包含复杂的控制语句
  • 对函数作 inline 声明,只是程序设计者对编译系统的一个建议,而不是指令性的,编译系统会根据具体情况决定是否这样做

4.8 函数的重载

C++允许用同一函数名定义多个函数,而这些函数的参数个数参数类型可以不同,这就是函数的重载

所谓重载,就是“一物多用”。

int max(int a, int b);
int max(int a, int b, int max);
int max(double a, double b);
double max(int a, int b);//错误,与第一个矛盾,参数个数和参数类型都相同

4.9 函数的模板

所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。

通用函数定义:

template <typename T>	//模板声明

4.10 有默认参数的函数

指定默认值的参数必须放在形参列表的最右端

int max(int a = 1, int b, int c);//错误
int max(int b, int c, int a = 1);//正确

注意事项:

  • 如果函数的定义在函数调用之前,则应在函数定义中给默认值;
  • 如果函数的定义在函数调用之后,则应有函数声明中给出默认值;
  • 最好二者选其一,不要同时在函数定义和函数声明给出默认值,如果给了,以编译器最先遇到的为准;
  • 一个函数不能既作为重载函数,又作为有默认参数的函数,如果少写一个参数,出现无义性,系统无法执行;
int max(int a, int b, inb c = 100);
//此时调用max(5,23)
maxNum = max(5,23);//程序报错

4.11 局部变量和全局变量

每个变量都有其作用范围,这就是变量的作用域

4.11.1 局部变量

在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,这些内部变量称为局部变量

  • 不同函数中也可以使用同名的变量,它们代表不同的对象,互不干扰;
  • 可以在一个函数内的复合语句中定义变量,这些变量只在复合语句有效
  • 形参也是局部变量;
  • 在函数原型的声明中出现的参数名,只在原型声明中的括号内有效,它不是实际存在的变量,不能被引用

4.11.2 全局变量

在函数之外定义的变量,称为全局变量

全局变量的有效范围从定义变量的开始到本源文件结束。

注意事项:

  • 设置全局变量的作用是增加了函数间数据联系的渠道;
  • 不在必要时不要使用全局变量;
    • 全局变量在程序的全部执行过程中都占用内存空间;
    • 它使函数的通用性降低了,因为在执行函数时要受到外部变量的影响
    • 在程序设计时,要求模块的内聚性强、与其他模块的耦合性弱;
    • 使用全局变量过多,会降低程序的清晰性
    • 如果在同一个源文件中,全局变量与局部变量同名,则在局部变量的作用范围内,全局变量被屏蔽

变量的有效范围称为变量的作用域

  • 文件作用域
  • 函数作用域
  • 块作用域
  • 函数原型作用域

4.12 变量的存储类别

4.12.1 动态存储方式与静态存储存储方式

变量的两种属性:

  • 作用域
  • 存储期(生命周期)
    • 静态存储期——系统对变量分配固定的存储空间(全局变量)
    • 动态存储期——动态分配存储空间(函数形参、定义的变量、函数调用时的现场保护和返回地址)
  • 存储类别
    • 自动的 auto
    • 静态的 static
    • 寄存器的 register
    • 外部的 extern

4.12.2 自动变量

函数中的局部变量,如果不用关键字 static 加以声明,编译系统对它们是动态地分配存储空间的。

这类局部变量称为自动变量

4.12.3 用 static 声明静态局部变量

局部变量的值在函数调用结束后而不消失而保留原值。

注意事项:

  • 静态局部变量在静态存储内分配存储单元;
  • 对静态局部变量是在编译时赋初值的,即赋初值一次,以后每次调用时不再重新赋值而只是保留上一次函数调用结束的值;
  • 如果静态局部变量没有赋初值,则编译时自动赋值为0或空字符;

4.12.4 用 register 声明寄存器变量

如果有一些变量使用频繁,则为存取变量的值要花不少的时间,为提高执行效率,C++允许将局部变量的值放在CPU的寄存器中,需要时直接从寄存器取出参与运算

4.12.5 用 extern 声明外部变量

  1. 在一个文件内声明全局变量

    #include<iostream>
    using namespace std;
    
    int max(int, int);		//函数声明
    int main()
    {
        extern int a, b;	//对全局变量a,b作提前引用声明
        cout<<max(a,b)<<endl;
    }
    
    int a = 15, b = -7;		//定义全局变量
    
    int max(int x, int y)
    {
        int z;
        z = x > y ? x : y;
        return z;
    }
    
  2. 在多文件的程序中声明外部变量 在任一个文件中定义外部变量 num ,而在另一文件中用 extern 对 num 作外部声明

4.12.6 用 static 声明静态外部变量

在程序设计中希望某某些外部变量只限于被本文件引用,而不能被其他文件引用

4.13 变量属性小结

变量的属性:

  • 存储类别:auto static register extern 四种存储类别;
  • 作用域:指在程序中可以引用该变量的区域;
  • 存储期:指变量在内存的存储周期;

从不同角度分析它们之间的联系:

1、从作用域角度分,有局部变量和全局变量——从空间的角度

局部变量:

  • 自动变量,即动态局部变量
  • 局部变量(离开函数,值仍保留)
  • 寄存器变量(离开函数,值就消失)
  • 形式参数

全局变量:

  • 静态外部变量(只限本文件引用)
  • 外部变量(即非静态的外部变量,允许其他文件引用)

2、从变量存储期来区分,有动态存储和静态存储——从时间的角度

动态存储:

  • 自动变量
  • 寄存器变量
  • 形式参数

静态存储

  • 静态局部变量
  • 静态外部变量
  • 外部变量

3、从变量值存放的位置来区分

静态存储区:

  • 静态局部变量
  • 静态外部变量
  • 外部变量

动态存储区:

  • 自动变量
  • 形式参数

寄存器:

  • 寄存器变量

变量的可见性:在此作用域内可以引用该变量,又称为变量在此作用域可见。

static 声明:

  • static 声明对局部变量来说,static 使变量由动态存储方式变为静态存储方式;
  • 而对全局变量来说,它使变量局部化。本来全局变量在多文件均可使用,现在只能在本源程序使用,但仍为静态存储方式;
  • 从作用域角度来看,凡是有 static 声明的,其作用域都是局限的,或者局限在本函数内,或者局限在本文件内;

4.14 关于变量的声明和定义

函数由两部分组成:

  • 声明部分——对有关的标识符的属性进行说明;
  • 执行语句;

函数的声明是函数的原型,函数的定义是函数功能的确立。

对变量来说,分为以下两种:

  • 需要建立存储空间的,int a——定义性声明(定义)
  • 不需要建立存储空间的, extern int a;——引用性声明(声明)
int main()
{
    extern int a;	//外部变量声明
}

int a;				//外部变量定义
  • 外部变量定义只能有一次;
  • 外部变量声明可以有很多次,哪个函数要使用,就在哪个函数内声明;

4.15 外部函数

函数本质上是全局的,因为一个函数要被另外一个函数调用,但是,也可以指定函数只能被本被本文件调用,因此可以将函数分为内部函数和外部函数;

4.15.1 内部函数

内部函数(静态函数):一个函数只能被本文件中其他函数所调用;

一般格式:

static 类型标识符 函数名(形参表)

4.15.2 外部函数

如果在函数首部的最左端冠以关键字 extern ,则表示此函数是外部函数,可供其他文件调用;

extern 类型标识符 函数名(形参表)

//外部函数max
int max(int x, int y)
{
	int z;
	z = x > y ? x : y;
	return z;
}

//调用外部函数max
#include<iostream>
using namespace std;

int main()
{
	extern int max(int, int);//声明要调用其他文件的外部max函数

	int a, b;
	cin >> a >> b;
	cout << max(a, b) << endl;
	return 0;
}

4.16 头文件

4.16.1 头文件的内容

  • 对类型的声明
  • 函数的声明
  • 内置函数的定义
  • 宏定义
  • 全局变量定义
  • 外部变量声明
  • 其他头文件

4.16.2 关于C++标准库和头文件的形式

第5章 利用数组处理批量数据

5.1 为什么需要数组

所谓数组,就是用一个统一的名字代表这批数据,而序号或下标来区分各个数据。

数组是有序数的集合;

5.2 定义和引用一维数组

5.2.1 定义一维数组

定义数组的一般形式:

类型名 数组名[常量表达式]

const int n = 5;
int a[10];
int a[2*5];
int a[n*2];		//前面已经定义了常变量n

常量表达式可以是常量、常变量或符号常量,但不能包含变量。

也就是说,不允许对数组的大小作动态定义

5.2.2 引用一维数组的元素

数组必须先定义,再引用。只能逐个引用数组元素的值而不能一次引用整个数组中的全部元素的值。

数组元素的表示形式:

数组名[下标]

#include<iostream>
using namespace std;

int main()
{
	int i, a[10];
	for (i = 0; i < 10; i++)
		a[i] = i;
	for (i = 9; i >= 0; i--)
		cout << a[i] << " ";
	cout << endl;

	return 0;
}

5.2.3 一维数组的初始化

  • 在定义数组时对全部元素赋初值

    int a[10];
    a[10] = { 0,1,2,3,4,5,6,7,8,9 };
    
  • 给一部分元素赋值,其他元素默认为0

    a[10] = { 0,1,2,3,4 };
    
  • 对全部元素赋初值时,不指定数组长长

    a[] = { 0,1,2,3,4,5 };
    

5.2.4 一维数组程序举例

#include<iostream>
using namespace std;

int main()
{
	int a[10];
	int i, j, temp;
	int n = sizeof(a) / sizeof(a[0]);

	cout << "please enter 10 numbers: " << endl;

	for (i = 0; i < 10; i++)
	{
		cin >> a[i];
	}
	cout << endl;

	for (j = 1; j <= n-1; j++)		//共进行n-1轮比较
	{
		for (i = 0; i <= n - j - 1; i++)	//在每轮中要进行(n-j)次两比较
		{
			if (a[i] > a[i + 1])
			{
				temp = a[i];
				a[i] = a[i+1];
				a[i+1] = temp;
			}
		}
	}
	cout << "the sorted numbers: " << endl;
	
	for (i = 0; i < 10; i++)
	{
		cout << a[i] << " ";
	}
	cout << endl;
	return 0;
}

5.3 定义和引用二维数组

5.3.1 定义二维数组

定义二维数组的一般形式:

类型名  数组名[常量表达式][常量表达式]

C++中,二维数组中元素先按行存放,再放列元素。

C++允许使用多维数组

5.3.2 引用二维数组的元素

一般形式:

数组名 [下标][下标]

数组元素是左值,可以出现在表达式中,也可以被赋值

a[1][2] = a[2][3]/2;
a[2][3] = 15;

5.3.3 二维数组的初始化

  • 按行给二维数全部元素赋初值

    int a[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };
    
  • 将所有数据写在一个花括号内,按数组排列的顺序对全部元素赋初值

    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    
  • 对部分元素赋初值

    int a[3][4]={{1}, {5}, {9}};
    
  • 对全部元素赋初值,可以省略行的常量表达式

    int a[][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    

5.3.4 二维数组程序举例

二维数行列互换

#include<iostream>
using namespace std;

int main()
{
	int i, j, max;
	int row, column;
	int a[3][4] = { 12,1,3,5,6,8,9,10,11,7,4,2 };
	max = a[0][0];		//给max一个初值
	row = 1;		//行列初值
	column = 1;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			if (max < a[i][j])
			{
				max = a[i][j];
				row = i+1;
				column = j+1;
			}
		}
	}
	cout << "max = " << max << endl;
	cout << "row = " << row << endl;
	cout << "column = " << column << endl;

	return 0;
}

5.4 用数组作函数参数

数组名也可以作实参和形参,传递的是数组的起始地址

void select_sort(int arrar[], int n)
{
    语句1;
}

对数组名作函数参数的说明:

  • 如果函数实参是数组名,那么形参也应为数组名;
  • 数组名代表数组首元素的地址,并不代表数组中的全部元素;
  • 在用数组名作函数实参时,如果改变最形参数组元素的值将同时改变实参数组元素的值

5.5 字符数组

5.5.1 定义和初始化字符数组

char ch[]={'I','a','m','b','o','y'};

5.5.2 字符数组的赋值与引用

只能对字符数组的元素赋值,而不能用赋值语句对整个数组赋值;

5.5.3 字符串和字符串结束标志

char str[12] = {'I','a','m','h','a','p','p','y'};

这个字符串的实际长度是10,与数组长度12并不相等,此时系统对字符数组最后两个元素自动补空字符'\0'

字符串结束标志——'\0'

在程序中,往往依靠检测'\0'的位置来判定字符串是否结束,而不是根据数组的长度来决定字符串长度

可以用字符串常量来初始化字符数组:

char str[] = {"I am happy"};
char str[] = "I am happy";//数组字符串的长度为11
char str[] = {'I','a','m','h','a','p','p','y','\0'};

5.5.4 字符数组的输入输出

5.5.5 使用字符串处理函数对字符串进行操作

  • 字符串连接函数 strcat(string catenate)

    //函数原型
    strcat(char[], const char[]);
    

    第二个形参用const修饰表示该参数有默认值,所以放在最右边

  • 字符串复制函数 strcpy

    //函数原型
    strcpy(char[], const char[]);//函数报错,在项目属性加上/D "_CRT_SECURE_NO_WARNINGS" 
    
  • 字符串比较函数 strcmp

    //函数原型
    strcmp(const char[], const char[]);
    strcmp(str1, str2);
    
    • 如果字符串1等于字符串2,函数值为0
    • 如果字符串1大于字符串2,函数值为正整数
    • 如果字符串1小于字符串2,函数值为负整数
  • 字符串长度函数 strlen

    //函数原型
    strlen(const char[]);
    strlen(str1);
    

5.5.6 字符数组应用举例

#include<iostream>
using namespace std;

int main()
{
	void smallest_string(char str[][30], int n);//函数声明
	int i;
	char country_name[3][30];

	for (i = 0; i < 3; i++)
	{
		cin >> country_name[i];
	}
	smallest_string(country_name, 3);

	return 0;
}

void smallest_string(char str[][30], int n)
{
	int i;
	char string[30];
	strcpy(string, str[0]);
	for (i = 0; i < n; i++)
	{
		if (strcmp(str[i], string) < 0)
		{
			strcpy(string, str[i]);
		}
	}
	cout << endl << "the smallest string is " << string << endl;
}

5.6 字符串类与字符串变量

string 并不是C++语言本身具有的基本类型,它是在C++标准库中声明的一个字符串类

第6章 善于使用指针与引用

6.1 什么是指针

内存区的每一个字节有一个编号,这就是“地址”,它相当于旅馆中的房间号。

图片1

间接存取:将变量i的地址存放在另一个变量中,可以定义这样一种特殊的变量,它是专门用来存放地址的。

i_pointer = &i;//变量i的地址,即2000

&是取址运算符

指针变量:专门用来存放地址的变量

一个变量的地址就是该变量的指针

指针和指针变量的区别:

  • 指针:指针变量的值
  • 指针变量:存放变量地址的变量

6.2 变量与指针

6.2.1 定义指义变量

基类型 指针变量名;*

int i, j;
int* p1, * p2;
p1 = &i;
p2 = &j;

注意:指针变量名是p1,而不是*p1

  • 在定义指针变量时必须指定基类型;

  • 不能用一个整数给指针变量赋值;

    int* pointer = 2000;//错误写法
    
  • 一个指针变量只能指向同一个类型的变量

  • 完整地说,a是指向整型数据的指针变量

一个变量的指针包含两方面的含义:

  • 以存储单元编号表示的地址;
  • 指向的存储单元的数据类型;

因此,可以说指针变量是基本数据类型派生出来的类型

6.2.2 引用指针变量

*:间接访问运算符

&:取址运算符

&*p1;//首先*p1 = a,所以式子等价于&a
*&p1;//首先&p1是对指针变量取地址,再解引用,等价于p1

6.2.3 用指针作函数参数

1、通过实参传递到形参交换变量的值(值传递)

int main()
{
	int a, b;
	cin >> a >> b;	//输入a=45,b=78

	void swap(int x, int y);
	if (a < b)
	{
		swap(a, b);
	}
	cout << "max = " << a << "min = " << b << endl;	//输出max = 45, min = 78
}

void swap(int x, int y)
{
	int temp;
	temp = x;
	x = y;
	y = temp;
    //retun x, y;  //除非return语句返回多个值,然而这显然是不存在的,所以直接交换变量的值行不能(只要存在函数调用)
}

从输出结果可以看出,实参的顺序并没有发生改变

图片2

由于虚实结合是采取单向“值传递”方式,只能从实参向形参传数据,形参值的改变无法回传给实参。

2、通过指针改变变量a,b的值

//2、通过指针交换变量a,b的值
int main()
{
	int* p1, * p2;
	int a, b;
	cin >> a >> b;	//输入a=45,b=78

	p1 = &a;
	p2 = &b;

	void swap(int*, int*);
	if (a < b)
	{
		swap(p1, p2);
	}

	cout << "max = " << a << "min = " << b << endl;		//输出max = 45, min = 78
	return 0;
}
void swap(int* p1, int* p2)
{
	int temp;
	temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
指针指向的值交换

交换a和b的值,而指针变量p1和p2的值不发生改变,即指针的指向不发生改变

回传过程中,实参指针的值(即地址值)没有改变,改变的是实参指针变量所指向变量的值

3、交换指针变量的值(即指针的指向发生改变)

//3、交换指针变量的值(即改变指针的指向)
int main()
{
	int* p1, * p2;
	int a, b;
	cin >> a >> b;

	p1 = &a;
	p2 = &b;

	void swap(int*, int*);
	if (a < b)
	{
		swap(p1, p2);
	}

	cout << "max = " << a << "min = " << b << endl;
	cout << "max = " << *p1 << "min = " << *p2 << endl;
	return 0;
}
void swap(int* p1, int* p2)
{
	int* temp;
	temp = p1;
	p1 = p2;
	p2 = temp;
}
指针指向发生改变

如上图,在最后一步,形参值的改变是否能回传到实参呢?

显然,这是不可以的,在函数调用中,值传递是单向的,形参值的变化不会回传到实参,所以实参的值没有发生改变。

所以,为了改变变量的值,我们通过曲线救国的方式,函数形参为指针变量,而在函数体中可以改变指针变量所指向变量的值。此过程中,实参指针变量的值没有发生改变,只不过它们指向的值发生了改变

6.3 数组与指针

6.3.1 指向数组元素的指针

所谓数组元素的指针就是数组元素的首地址。

int a[10];
int* p;
//以下两个语句等价,都是将数组的首地址赋给指针变量p
p = &a[0];
*p = a[0];
p = a;
//p + 1 = &a[1];

如果指针变量p已指向数组的一个元素,则p+1指向同一数组的下一个元素

引用数组元素的3种方法:

  • 下标法
  • 指针法
  • 用指针变量指向数组元素
cout<< a[i] <<endl;
cout<< *(a+i) <<endl;
cout<<* (p+i) <<endl;

以上三者等价,但是

  • 方法1和方法2执行效率是相同的,方法3比方法1和2快,用指针变量直接指向元素,不必每次都重新计算地址;
  • 用直标法比较直观,能直接知道是第几个元素

注意事项:

指针变量p是被定义为指向整型的对象,它可以指向整型的数组元素,也可以指向数组以后的内存单元

int a[10];
int* p;
p = a;
cout<<* (p+10);	//输出的是a[10]的值,也就量数组的每11个数,但是数组没有第11个数

此操作虽然编译是合法的,但是避免出现这样的情况。

6.3.2 用指针变量作函数形参接收数组地址

用数组名作函数的参数,传递的是数组首元素的地址。

C++编译系统将形参数组名一律作为指针变量来处理。

void bubbleSort(int array[], int n);
void bubbleSort(int* array, int n);
//两者写法完全等价

因此,在调用数组作为实参的函数中,实参和形参有4种结合方式:

实参形参
数组名数组名
数组名指针变量
指针变量数组名
指针变量指针变量

实参数组名a代表一个固定的地址,或者说是指针型常量,因此a的值是不可能改变的;

形参数组名是指针变量,并不是一个固定的地址值,它的值是可以改变的;

6.4 字符串与指针

#include<iostream>
#include<string>
using namespace std;

int main()
{
	char* p;
	char str[] = "I love China.";
	p = str;
	cout << *p << endl;		//I

	string str2 = "I love China.";	
	cout << str2 << endl;		//I love China.

	return 0;
}

6.5 函数与指针

指针变量也可以指向一个函数,这个函数入口地址就称为函数的指针

#include<iostream>
using namespace std;

int main()
{
	int a, b, m;
	cin >> a >> b;

	int max(int, int);
	m = max(a, b);
	cout << "max = " << m << endl;
	return 0;
}

int max(int x, int y)
{
	int z;
	z = x > y ? x : y;
	return z;
}

指向函数的指针变量的一般定义形式:

*函数类型 (变量名)(函数形参表)

int max(int x, int y);	//函数声明
int (*p)(int, int);		//定义指向函数的指针变量p
p = max;		//使p指向函数max
m = p(a,b);
#include<iostream>
using namespace std;

int main()
{
	int a, b, m;
	cin >> a >> b;

	int max(int, int);
	int (*p)(int, int);
	p = max;
	m = p(a, b);
	cout << "max = " << m << endl;
	return 0;
}

int max(int x, int y)
{
	int z;
	z = x > y ? x : y;
	return z;
}

6.6 返回指针值的函数

一个函数可以返回整型值、字符值、浮点型值,也可以返回指针型的数据,即地址

返回指针值的函数简称为指针函数

定义指针函数的一般形式:

*类型名 函数名(参数列表)

int* a(int x, int y)
{
    
}

6.7 指针数组和指向指针的指针

6.7.1 指针数组

如果一个数组,其元素均为指针类型数据,该数组称为指针数组

定义形式:

*类型名 数组名[数组长度]

int* p[4];

6.7.2 指向指针的指针

char* (*p);
char **p;

6.8 const指针

1、指向常量的指针变量

一般形式:

const 类型名 指针变量名;*

int a = 12;
int b = 15;
const int* p = &a;//因为a是常量,因此不可以通过p改变a的值
p = &b;			  //合法,p指向b
a = 20;			//直接改变a的值,合法
//如果想绝对保证a的值始终不变,那么应该把a定义为常变量
const int a = 12;

定义常量指针的目的:

指向常量的指针变量用作函数形参,以防止指针形参所指对象的值发生改变影响实参

如下面两个程序:

//1、未使用常量指针
int main()
{
	int a = 10;
	void fun(int*);
	fun(&a);
	cout << a << endl;
	return 0;
}

void fun(int* p)
{
	*p = 5 * (*p);	//形参所指向的值发生改变
}
int main()
{
	int a = 10;
	void fun(int*);
	fun(&a);
	cout << a << endl;
	return 0;
}

void fun(const int* p)
{
	*p = 5 * (*p);	//程序报错,表达式必须是可修改的左值,说明*p的值不可通过指针改变
}

2、常指针

指定指针变量的值是常量,即指针变量的指向不能改变。

定义形式:

类型名 const 指针变量名;*

  • 常指针变量
  • 必须在定义时初始化,指定其指向
  • 指针变量的指向不能改变,但指针变量指向的值可以发生改变
int a = 4;
int b = 6;
int* const p = &a;//p-->a
p = &b;//p-->b,非法操作

3、指向常量的常指针

int a = 10;
int b = 12;
const int* const p = &a;

6.9 void指针类型

可以定义一个基类型为void的指针变量(即void* 型变量),它不指向任何类型的数据。

它应理解为“指向空类型 “或“不指向确定的类型”的数据。

int a = 3;
int *p1 = &a;		//p1指向int型变量
char* p2 = "new";	//p2指向char型变量
void* p3;			//p3为无类型指针变量
p3 = (void*)p1;		//将p1的值转换为void*类型,然后赋值给p3
cout<<*p3<<endl;	//p3不能指向确定类型的变量,*p3非法
cout<<*(int*)p3<<endl;//把p3的值强转为int*型,可以指向变量a

6.10 有关指针的数据类型和指针运算的小结

6.10.1 有关指针的数据类型的小结

6.10.2 指针运算小结

  • 指针变量加减一个整数 p+i等价于p+i*d,d为数据类型所占的字节数

  • 指针变量赋值

    p = &a;		//将变量a的地址赋给p
    p = array;	//将数组array的首元素的地址赋给p
    p = &array[i];
    

p = max; //将函数max的入口地址赋给p p1 = p2; //p1和p2是同类型指针,将p2的值赋给p1


* 指针变量可以有空值,即该指针变量不指向任何变量

```C++
p = NULL;

p的值等于NULL和p未被赋值是两个概念,前者是有值的,不指向任何变量,后者的值是一个无法预料的值

  • 两个指针可以相减 如果指针变量指向同一数组元素,则两个指针之变量值之差就是两个指针之间的元素个数

    p1 --> a[1];
    p2 --> a[4];
    p2 - p1 = 3;
    p1+p2;	//毫无意义
    
  • 两个指量比较 如果是指向同一数组的元素,则可以进行比较,否则比较毫无意义

  • 对指针变量的赋值应注意类型问题,如果不同类型,可以使用强制类型转换

6.11 引用

6.11.1 什么是变量的引用

对一个数据可以建立一个引用,它的作用是为一个变量起一个别名。这是C++对C的一个重要扩充。

int a;			//定义一个整型变量a
int &b = a;		//声明b是a的“引用”

注意事项:

  • 引用不是一种独立的数据类型,对引用只有声明,没有定义。

  • 声明引用时,必须同时使之初始化,即声明它代表哪一个变量

  • 在声明一个引用后,不能再使之作为另一变量的引用;(唯一性)

  • 不能建立引用数组;

    int a[5];
    int &b = a;		//错误,不能建立引用数组
    int &b = a[0];	//错误,不能作为数组元素的别名
    
  • 不能建立引用的引用,也没有引用的指针;

  • 可以取引用的地址;

    int* p1;
    int a;
    int &b = a;
    p1 = &b;	//&b就是&a
    
  • 区别引用声明符&和地址运算符&

  • 引用其实就是一个指针常量,它的指向不能改变,引用的本质还是指针

6.11.2 引用的简单使用

#include<iostream>
#include<iomanip>
using namespace std;

int main()
{
	int a = 10;
	int& b = a;
	a = a * a;
	cout << a << setw(6) << b << endl;//a的值改变了,b的值也应该改变

	b = b / 5;
	cout << b << setw(6) << a << endl;

	return 0;
}

6.11.3 引用作为函数参数

C++之所以增加引用机制,主要是把它作为函数参数,以扩充函数传递数据的功能。

问题:实现变量互换的3种方法

1、形参的值互换,该变化不能回传给实参,最终不能实现互换

#include<iostream>
using namespace std;

int main()
{
	int i = 3, j = 5;
	void swap(int a, int b);
	swap(i, j);
	cout << i << " " << j << endl;//输出3 5,i和j的值并未互换,值传递是单向传递的

	return 0;
}

void swap(int a, int b)
{ 
	int temp;
	temp = a;
	a = b;
	b = temp;
}

2、形参为指针变量,通过指针互换指针变量所指向的整型变量的值

#include<iostream>
using namespace std;

int main()
{
	int i = 3, j = 5;
	void swap(int* a, int* b);
	swap(&i, &j);
	cout << i << " " << j << endl;//输出5 3,i和j的值互换,传的值是地址,仍然是“值传递”

	return 0;
}

void swap(int* a, int* b)
{ 
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;
}

3、引用作形参,传递的是地址

#include<iostream>
using namespace std;

int main()
{
	int i = 3, j = 5;
	void swap(int& a, int& b);
	swap(i, j);
	cout << i << " " << j << endl;//输出5 3,i和j的值互换,传的值是地址

	return 0;
}

void swap(int& a, int& b)	//引用本质是指针常量,指向的变量不发生改变
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}

以上3种方式的异同:

  • 前两种方式传递的是实参的值,只不过第一种传递是的实参变量的值,第2种传递的是实参变量的地址值,传值方式
  • 在第3种方式中,实参是变量名,传递的是变量的地址,是传址方式

第7章 用户自定义数据类型

7.1 结构体类型

7.1.1 为什么需要用结构体类型

在一个组合项中包含若干个类型不同的数据项,C++允许用户自己指定这样的一种数据类型,它称为结构体.

结构体类似一个小型的类,成员表对应类中的成员变量

struct Student		//声明一个结构体类型Student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
}

7.1.2 结构体类型变量的定义方法及其初始化

  1. 定义结构体变量的3种方法

    • 先声明结构体类型再定义变量

      struct Student
      {
          成员表;
      }
      
      Student stu1, stu2;
      
    • 在声明类型的同时定义变量

      struct Student
      {
          成员表;
      }stu1, stu2;
      
  2. 结构体变量的初始化

    Student stu1 = {10002,"Wang Li",'M',20,1998,"Beijing"};
    

7.1.3 引用结构体变量

  • 可以将一个结构体的变量的值赋给另一个具有相同结构的结构体变量。

    stu1 = stu2;
    
  • 使用句点表示法对变量的成员赋值

    stu1.num  = 10010;
    

第8章 类和对象的特性

8.1 面向对象程序设计方法概述

  • C++并不是一种纯粹的面向对象的语言,而是一种基于过程和面向对象的混合型的语言;
  • 对于规模较小的程序,我们可以直接编写出一个基于过程的程序;
  • C++面向对象的机制就是为了解决编写大程序中的困难而产生的;
  • 在基于过程的程序中,函数是构成程序的基本部分,程序是面对的是一个个函数;
  • 每个函数都是独立存在于程序中,除了主函数只能被系统调用外,各函数可以互相调用;
  • 在面向对象中,除主函数外,其他函数基本上都是出现在“类”中,只有通过类才能调用类中的函数;
  • 程序的基本构成是单位是类,程序面对的是一个一个的类和对象;
  • 程序设计的主要工作是设计类、定义和使用类对象。
  • 面向对象程序设计有4个特点:
    • 抽象
    • 封装
    • 继承
    • 多态

8.1.1 什么是面向对象的程序设计

  1. 对象 任何一个对象都应当具有两个要素:即属性和行为。对象是由一组属性和一组行为构成的。
  2. 封装和信息隐蔽
    • 将有关的数据和操作代码封装在一个对象中,形成一个基本单位,各个对象之间相对独立,互不干扰;
    • 隐蔽部分细节,只留下少量接口,以便与外界联系,C++类对象中的函数名就是对象的接口
  3. 抽象
    • 比如,我们常用的“人”就是一种抽象,“中国人”也是;
    • 抽象的作用是表示同一类事物的本质
    • 类是对象的抽象,而对象则是类的实例化;
  4. 继承与重用
    • 例如,白马继承了马的所有特征,又增加了新的特征(颜色);
    • “马”是父类,“白马”是子类;
    • 使用继承机制,可以实现代码复用,提高的编写代码的效率;
  5. 多态性 所谓多态性是指,由继承而产生的不同的派生类,其对象对同一消息作出的不同的响应。多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性。

8.1.2 面向对象程序设计的特点

两个方面:

  • 设计所需的各种类和对象,即决定了把哪些数据和操作封装在一起;
  • 二是考虑怎样向有关对象发送消息,以完成所需的任务;

8.1.3 类和对象的作用

在基于过程的程序设计中:

程序 = 算法 + 数据结构

在面向对象的程序设计中:

对象 = 算法 + 数据结构

程序 = (对象1 + 对象2 + …)+ 消息

8.1.4 面向对象的软件开发

面向对象的软件工程包括以下几个部分:

  1. 面向对象分析
  2. 面向对象设计
  3. 面向对象编程
  4. 面向对象测试
  5. 面向对象维护

8.2 类的声明和对象的定义

8.2.1 类和对象的关系

类是对象的抽象,而对象是类类型的一个变量。(可以说类是对象的模板)

8.2.2 声明类类型

class Student	//以class开头,类名是Student,类名首字母要大写
{
    private:	//声明以下内容为私有
    int num;
    char name[20];
    char sex;	//以上3行是数据成员
    
    public:		//声明以下内容为公用的
    void display()	//这是成员函数
    {
        cout<<"num:"<<num<<endl;
        cout<<"name:"<<name<<endl;
        cout<<"sex"<<sex<<endl;
    }
};
//定义类对象
Student stu1,stu2;

如果在类的定义中既不指定private,也不指定public,则系统就默认为是私有的

成员访问限定符:

  • public
  • private(只能被本类中的成员函数引用,类外不能调用)
  • protected(受保护的成员,不能被类外成员访问,但可以被派生类的成员函数访问)

成员访问限定符作用范围:

直到出现另一个访问限定符或类体结束为止。

为了使程序清晰,使每一种成员访问限定符在类定义体中只出现一次,一般先写public部分,再写private部分。

8.2.3 定义对象的方法

  1. 先声明类类型,然后再定义对象

    • class 类名 对象名

      class Student stu1,stu2;	//从c继承下来的写法
      
    • 类名 对象名

      Student stu1, stu2;	//C++特色写法,多用这种
      
  2. 在声明类的同时定义对象

    class Student
    {
    	public:
        void display()
        {
            cout<<"num:"<<num<<endl;
            cout<<"name:"<<name<<endl;
            cout<<"sex"<<sex<<endl;
        }
        
        private:	//声明以下内容为私有
        int num;
        char name[20];
        char sex;	//以上3行是数据成员
    }stu1,stu2;	//声明类的同时定义两个类对象
    

8.3 类的成员函数

8.3.1 成员函数的性质

类的成员函数(简称类函数)是函数的一种,它可以被指定为public, private, protected的

8.3.2 在类外定义成员函数

class Student	//以class开头,类名是Student,类名首字母要大写
{
    public:		//声明以下内容为公用的
    void display();	//这是成员函数
    
    private:	//声明以下内容为私有
    int num;
    char name[20];
    char sex;	//以上3行是数据成员
};
//在类外定义成员函数
void Student::display()
{
        cout<<"num:"<<num<<endl;
        cout<<"name:"<<name<<endl;
        cout<<"sex"<<sex<<endl;
}

Student stu1, stu2;

void Student::display()中的::是作用域限定符或者作用域运算符,用它声明函数是哪个类的。

类函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前,否则会编译出错。

**好习惯:**在类的内部对成员函数作声明,而类体外定义成员函数(这样使类的接口和类的实现细节分离)

8.3.3 内置成员函数

类的成员函数也可指定为内置函数

  • C++要求对一般的内置函数要用关键字inline声明,但对类内定义的成员函数,可以省略 inline ,因为这些成员函数已被隐含地指定为内置函数

注意:如果成员函数在类体外定义,系统并不把它默认为内置函数,这时需要作显示 inline 声明

注意:如果在类体外定义 inline 函数,则必须将类定义和成员函数的定义都放在同一个头文件中,否则编译时无法进行置换。

但是这样做不利于类的接口与类的实现分离,不利于信息隐蔽。虽然执行效率高了,但从软件工程质量的角度来看,这样做不是好的办法。

8.3.4 成员函数的存储方式

用类去定义对象时,系统会为每一个对象分配存储空间。

class Time
{
    public:
    int hour;
    int minute;
    int sec;
    void set()
    {
        cin>>a>>b>>c;
    }
};

cout<<sizeof(Time)<<endl;	//输出结果是12=3(int)*4

一个对象所占的空间大小只取决于该对象中数据成员所占的空间,而与成员函数无关。

  • 不论成员函数在类内定义还是在类外定义,成员函数的代码段的存储方式是相同的,都不占用对象的存储空间;
  • 不要将成员函数的这种存储方式和inline内置函数混淆

8.4 成员函数的引用

8.4.1 通过对象名和成员运算符访问对象中的成员

stu1.num = 1001;
stu1.display();
//对象名  stu1
//成员运算符  .

在一个类中应当至少有一个公用的成员函数,作为对外的接口,否则就无法对对象进行任何操作。

8.4.2 通过指向对象的指针访问对象中的成员

class Time
{
	public:
    int hour;
    int minute;
};
Time t;
Time* p;
p = &t;
cout<<p->hour;	//输出p指向的对象中的成员hour
cout<<(*p).hour;
cout<<t.hour;
//以上三者等价

8.4.3 通过对象的引用来访问对象中的成员

Time t1;
Time& t2 = t1;
cout<<t2.hour;

8.5 类的封装和信息隐蔽

8.5.1 公用接口与私有实现的分离

公用成员函数是用户使用类的公用接口,或者说是类的对外接口;

通过成员函数对数据成员进行操作称为类的功能的实现;

类中被操作的数据是私有的,类的功能的实现细节对用户是隐蔽的,这种实现称为私有实现

类的公用接口与私有实现的分离,形成信息隐蔽;

当接口与实现分离时,只要类的接口没有改变,对私有实现的修改不会引起程序的其他部分的修改;

8.5.2 类声明和成员函数的分离

  • 在面向对象的程序开发中,往往把类的声明(包括成员函数的声明)放在指定的头文件中;
  • 可以认为类声明文件是用户使用类库的公用接口;
  • 包含成员函数定义的文件就是类的实现,类声明和成员函数定义是分别放在两个文件中的;

因此,一个C++程序是由3部分组成的:

  • 类声明文件.h
  • 类实现文件.cpp
  • 类的使用文件.cpp——主文件

在实际工作中,并不是将一个类声明做成一个头文件,而是将若干个常用的功能相近的声明集中在一起,形成各种类库:

  • C++编译系统提供的标准类库;
  • 用户自定义类库;

类库包含两个组成部分:

  • 包括类声明的头文件;
  • 已经过编译的成员函数的定义,它是目标文件

8.5.3 面向对象程序设计的几个名词

  • 方法(类的成员函数)
  • 消息(对象调用成员函数)
  • 对象(类的实例化)

第9章 怎样使用类和对象

9.1 利用构造函数对类对象进行初始化

9.1.1 对象的初始化

在定义一个对象时,也需要作初始化的工作,即对数据成员赋初值。

  • 如果一个类中所有的成员都是公用的,则可以在定义对象时对数据成员进委初始化:

    class Time
    {
    public:
        int hour;
        int minute;
        int sec;
    };
    Time t1 = {14, 56, 30};
    

    这种初始化和结构体变量的初始化是类似的

9.1.2 用构造函数实现数据成员的初始化

C++提供了构造函数来处理对象的初始化。

构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是建立对象时自动执行

构造函数的名这须与类名同名,不能任意命名,以便编译系统能识别它并把它作为构造函数来处理。

#include<iostream>
using namespace std;
class Time
{
public:
	//为了实现对象的初始化,定义构造函数Time()
	Time()
	{
		hour = 0;
		minute = 0;
		sec = 0;
	}
	void set_time();
	void show_time();
private:
	int hour;
	int minute;
	int sec;
};
void Time::set_time()
{
	cin >> hour;
	cin >> minute;
	cin >> sec;
}
void Time::show_time()
{
	cout << hour << ":" << minute << ":" << sec << endl;
}

int main()
{
	Time t1;
	t1.set_time();
	t1.show_time();
	Time t2;
	t2.show_time();
	return 0;
}

构造函数的注意事项:

  • 可以在类内定义构造函数,也可以在类外定义构造函数Time:Time( );

  • 在建立类对象时自动调用构造函数;

  • 构造函数没有返回值,因此也没有类型,它的作用只是对对象进行初始化;

  • 构造函数不需要用户调用,不也能被用户调用;

  • 可以用一个类对象初始化另一个类对象;

    Time t1;
    Time t2 = t1;
    
  • 在构造函数的函数体中不仅可以对数据成员赋初值,而且可以包含其他语句,但一般不提倡;

  • 如果用户没有定义构造函数,则C++系统会自动生成一个构造函数,只是这个构造函数的函数体是空的,也没参数,不执行初始化操作;

9.1.3 含参的构造函数

构造函数首部的一般形式:

构造函数名(类型1 形参1,形参2…)

Box(int h, int w, int len);

用户是不能调用构造函数的,因此无法采用常规的调用函数的方法给出实参,实参是在定义对象时给出的,定义对象的一般形式为:

类名 对象名(实参1,实参2)

Box mybox(12, 25, 30);//建立对象时同时指定数据成员的初值

9.1.4 用参数初始化表对数据成员初始化

//参数初始化表对数据成员初始化
Box::Box(int h,int w,int len):height(h),width(w),length(len){}
//类体内对数据成员初始化
Box(int h,int w,int len)
{
    height = h;
    width = w;
    length = len;
}

如果数据成员是数组,则应当在构造函数的函数体中用语句对其赋值,而不能在参数初始表中对其初始化。

9.1.5 构造函数的重载

在一个类中可以定义多个构造函数,以便对象提供不同的初始化方法,供用户选用。

这些构造函数具有相同的名字,而参数的个数或参数的类型不相同,这称为构造函数的重载

9.1.6 使用默认参数的构造函数

构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值。

注意事项:

  • 应在声明构造函数时指定默认值; image-20200820204747372
  • 在声明构造函数时,形参名可以省略,即写成 image-20200820205248809
  • 全部参数都指定了默认值的构造函数也属于默认构造函数,一个类只能有一个默认构造函数 image-20200820205855777
  • 在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数,容易造成歧义性

9.2 析构函数

析构函数是一个特殊的函数,析构函数是与构造函数作用相反的函数。

当对象的生命结束时,会自动执行析构函数:

  • 如果在一个函数中定义了一个对象,当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数;
  • 静态局部对象在函数调用结束时对象并不释放,因此也调用析构函数,只在main函数结束时,才调用static 局部对象的析构函数;
  • 全局对象
  • new

析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作;

析构函数不返回任何值,没有函数类型,也没有函数参数,因此也不能被重载;

一个类可以有多个构造函数,但是只能有一个析构函数;

还用来执行用户希望在最后一次使用对象之后所执行的任何操作

9.3 调用构造函数和析构函数的顺序

先构造的后析构,后构造的先析构(类似压子弹打枪的顺序,后压进去的子弹先打出去)

9.4 对象数组