C++等级考试知识点总结

第一章 C++语言概述

C++是一种面向对象的程序设计语言

  • 抽象数据类型
  • 封装和信息隐藏
  • 以继承和派生方式实现程序的重用
  • 以运算符重载和虚函数来实现多态性
  • 以模板来实现类型的参数化

C++是C语言的超集

  • C++继承了C语言简明、高效、灵活等众多优点
  • 以前使用C语言编写的大批软件可以不加任何修改,直接在C++开发环境下维护
  • C语言程序员只需要学习C++扩充的新特性,就可以很快地使用C++编写程序

面向对象程序设计的三个主要特征

  • 封装性
  • 继承性
  • 多态性

C++语言的基本符号

  • 字母:包括大字母和小写字母共52个符号
  • 数字:包括0-9共10个字符
  • 特殊符号:包括+ - * / = , . _ : ; ? \ “ ‘ ~ | ! # % & () [] {} ^ <> 和空格共30个符号。

C++语言的词汇

  • 关键字:也称保留字,它是由C++语言本身预先定义好的一类单词。其中是ANSI C标准规定的32个关键字,ANSI C++标准补充的29个关键字。
  • 标识符:是用户为程序中各种需要命名的“元素”所起的名字。这些元素包括:变量、符号、常量、函数、函数的参数、结构、类、对象等。
  • 标识符的命名规则
    • 标识符以一个字母或下划线开头的,由字母、数字、下划线组成的字符串
    • 不能与任意一个关键字同名
    • 标识符中的字母区分大小写
    • 标识符不宜过长
  • 字面常量
  • 运算符
  • 标点符号

C++程序的基本框架

  • 一个简单的C++程序

    1
    2
    3
    4
    5
    6
    #include <iostream>
    int main()
    {
    std::cout<<"This a simple C++ program.\n";
    return 0;
    }

    cout是C++中的标准输出流对象,它通常代表计算机屏幕

    cout在标准头文件iostream中被声明(注:标识符cout位于std名字空间中,须用前缀std::进行修饰)。

    “<<”是输出操作符,功能是将它右边的内容输出到它左边的指定设备上。

此程序的运行结果是在屏幕上显示:This a simple C++ program.

  • 注释:
    • //
    • /* */

C++程序的开发过程

  • 编辑
  • 编译
  • 连接
  • 运行和调试

第二章 数据类型、运算符和表达式

C++语言的数据类型

  • 基本类型

    • 逻辑性:用关键字bool表示,因此又称为bool型(布尔型)。逻辑型的取值只包括true和false,它们都是C++关键字。其中,true对应整数1,false对应整数0,表示逻辑假。

    • 字符型:字符型用关键字char表示,因此又称为char型。字符型的取值范围是全部基本字符以及ASCII码集或扩充ASCII码集对应的全部符号。字符型数据占用1字节(Byte),即8位(bit)空间。

    • 整型:整型用关键字int表示。16位计算机中,整型变量占2个字节,而在32位计算机中,整型变量占4个字节。

    • 浮点型:浮点型包括单精度和双精度,单精度用关键字float表示,双精度用关键字float表示。float型数据一般占用4字节(Byte),即32位(bit)空间;double型数据一般占用8字节,即64位空间。

    • 空值型:空值型用关键字void表示,空值型的取值为空。

  • 基本类型的派生类型

    派生类型声明符由基本类型关键字char、int、float、double前面加上类型修饰符组成。类型修饰符包括:

    • short 短类型,缩短字长
    • long 长类型,家常字长
    • signed 有符号类型,取值范围包括正负值
    • unsigned 无符号类型,取值范围包括正值

常量

常量是指在程序执行过程中值不改变的量。常量有两个表示形式,即字面常量和符合常量。字面常量的类型是根据书写形式来区分的,例如:15,-0.226,’a’,”Hello Word”等都是字面常量,他们的类型分别是:整型、浮点型、字符型、字符串型,每个字面常量的字面本身就是它的值。符号常量是一个标识符,在程序中必须遵循“先声明,后使用”的原则。

  • 逻辑常量:值是true或false。

  • 字符常量:简称字符,以单引号作为起止符号,中间有一个或若干个字符。例如,’a’和’&’,单引号中间有一个字符,这是一般意义上的字符常量;除此之外还有一种特殊形式的字符常量,例如:’\n’,’\146’,’\x6E’等以“\”开头的包括多个字符的字符序列也都是符合规定的字符常量。

  • 整型常量:十进制整数、八进制整数和十六进制整数。

  • 实型常量

    • 定点表示

      定点表示的实数简称定点数,即以小数形式表示实数。

      定点表示的实数由一个正好或负号(正号可以省略)后接若干个十进制数字和一个小数点组成,这个小数点可以处在任何一个数字位的前面或后面。

    • 浮点表示

      浮点标书的实数简称浮点数,即以指数形式表示实数。

      对于一个浮点数,若将它尾数中的小数点调整到最左边第一个非零数字的后面,则称它为规格化(或标准化)浮点数,这有些类似于数学中的科学计数法。例如41.6E8和-0.077E5是非规格化的。

      实型常量分为:单精度(float)、双精度(double)和长双精度(long double)。

  • 枚举常量:是枚举类型中定义的值,即枚举值。

    枚举类型的语法格式为:

    enum <类型名>{ <枚举值表> }
    

变量

变量是程序中用于存储信息的单元,它对应于某个内存空间。

  • 变量的定义:

    • 变量声明语句的一般格式:

      [<存储类>] <类型名> <变量名>[=<初值表达式>]
      
    • 存储类:有四种,分别是auto、register、static、extern,默认的存储类是auto。

    • 类型名:已存在的一种数据类型名称,如char、short、int、long、float、double等基本数据类型名,或者用户定义的数据类型名。

    • 变量名:用户定义的一个标识符,用来表示一个变量,该变量可以可以通过后面的可以选项赋予一个值,成为给变量赋初值。若变量名后不带有初始表达式,则不为变量赋予任何值,此时的变量值不确定。

    • 初值表达式:是一个表达式,它的值就是赋予变量的初值。

  • 变量的使用方式

    • 全局变量和局部变量

      • 全局变量:在所有函数定义、类定义和程序块之外声明的变量

      • 局部变量:在某个函数定义、类定义或程序块之内声明的变量

    • 生存期与作用域

    • 变量的存储类属性

      • auto变量:用关键字auto声明的局部变量称为自动变量。auto为变量声明时的默认存储类别,即在变量定义时,如果不显示标明存储类别,则系统自动按auto变量处理。auto变量所占用存储空间的分配和释放工作有系统自动完成。

      • register变量:用关键字register声明的局部变量称为寄存器变量。register变量可能以寄存器作为其存储空间。

      • static变量:用关键字static声明的变量称为静态变量

      • extern变量:用关键字extern声明的变量称为外部变量

    • typedef类型说明

      • 使用关键字typedef可以为已有类型名定义一个新类型名。
      • 语法格式

        typedef<已有类型名> <新类型名>

  • 符号变量声明语句

    • 符号常量在使用之前必须先进行定义。符号常量定义语句同变量定义语句类似,其语法格式为:

    • const <类型名> <符号常量名> = <初值表达式>,…;

运算符和表达式

运算符和表达式概念

  • C++语言中运算符可以根据其运算分量个数的多少分为单目(或一元)运算符双目(或二元)运算符三目(或三元)运算符3类。

运算类型与运算符

  • 每一种运算与其他运算的区别在于以下3个方面:

    • 参与运算的运算分量的数量和类型
    • 运算结果的数据类型
    • 运算的具体操作
  • 同一类运算可以组成一种运算类型,凡是具有相同运算分量和结果类型的运算划分为同一类运算,比如:算术运算符、关系运算符、逻辑运算、位运算等。

赋值运算

  • 赋值运算是一种双目运算,其形式为:

  • <变量名> = <表达式>

  • 赋值运算的具体操作作为:先计算右端表达式的值,然后把该值赋给左端变量

算术运算符和算术表达式

  • 算术运算是指int型、float型、double型(也包括char型)的数值类数据计算后,得到同一类型数据的运算。算术运算中所使用的运算符称为算术运算符。

    • 单目算术运算符包括:-(单减)、++(增量)和 –(减量)

      • 单减运算的格式为:-<运算分量>

      • 增量运算分为前缀和后缀增量

    • 双目算术运算符包括:+、-、*、/、%(取余)。

  • 算术表达式:由算术运算符(包括单目和双目)连接运算分量而组成的式子称为算术表达式。

关系运算符和关系表达式

  • C++语言提供了6种关系运算符,它们是:

    • < 小于
    • <= 大于等于
    • 大于
      
    • = 大于等于

    • == 等于
    • != 不等于
  • 关系运算的使用格式

    • <运算分量> <关系运算符> <运算分量>

逻辑运算符合逻辑表达式

  • C++语言提供了3种逻辑运算符,它们是:

    • ! 逻辑非
    • && 逻辑与
    • || 逻辑或
  • 逻辑运算符的使用格式为:

    • <逻辑运算符! ><运算分量>
    • <运算分量> <逻辑运算符&&或||> <运算分量>

位运算

  • C++提供6种运算符:
    • 双目位运算符:&(按位与)、|(按位或)、^(按位异或)、>>(按位右移)、<<(按位左移)
    • 单目位运算符:~(按位取反)

其他运算

  • 条件运算符

    • 使用格式:

      <表达式1>?<表达式2>:<表达式3>
      
  • 逗号运算符

    • C++中使用逗号运算符指明对多个表达式进行顺序求值
    • 使用格式:

      <表达式1>,<表达式2>,...,<表达式3>
      
  • sizeof

    • 使用运算符sizeof可以进行字长提取操作,因此sizeof运算符又称为字长提取符
    • 使用格式:

      sizeof(<运算分量>)
      

      字长提取运算的结果为一个整数,该整数表示指定的类型或变量的字节长度,即在内存中占用的字节(Byte)数。

  • 圆括号运算符

    • C++中不仅将圆括号()归为运算符,而且根据不同的使用方式,可以对圆括号运算符的功能作出以下3种不同的解释。
    • (1)圆括号用于函数调用,格式是:

      <函数名>(<实参表>)
      
    • (2)圆括号用于强制类型转换,格式是:

      (<类型名>)<表达式>
      
    • (3)圆括号用于类型构造,格式是:

      <类型名>(<表达式>)
      

优先级和结合性

  • C++中运算符的种类相当丰富,每个运算符的使用方法和操作方式也各不相同。

  • 根据运算符的优先级和结合性,可以将表达式的计算顺序规则总结为一下3条:

    • 有限计算带有括号的子表达式。

    • 在没有括号的部分,依照运算符优先级,有高到低进行计算。

    • 具有相同优先级的运算符,按照结核性规定,依次进行计算。

      运算符优先级:“==”高于“=”

第三章 基本控制结构

C++语句

  • 语句是C++程序中的基本功能单位。
  • C++语句按照不同功能大体分为6种类型:
    • 声明语句
    • 表达式语句
    • 选择语句
    • 循环语句
    • 跳转语句
    • 复活语句

顺序结构

  • 在一个没有选择和循环结构的程序中,语句将按照书写的先后顺序,从左到右,自上而下依次执行。

    • 声明语句

      • 变量声明

        char ch; //声明和定义char型变量

        int count = 1; //声明、定义和初始化int型变量

        extern int error_num; //声明int型变量

      • 常量声明

        const int MAX_LEN = 128; //声明、定义和初始化int型常量

      • 函数声明

        double sqrt(double);

      • 类型声明

        typedef unsigned int ID; //声明和定义类型

        enmu Color{RED,GREEN,BLUE}; //声明和定义枚举

        struct Date{int y,m,d}; //声明和定义结构

        class Employee; //声明类

    • 表达式语句

      • C++所有对数据的操作和处理工作都是通过表达式语句来完成的。
    • 基本输入输出

      • cout<<Expr; //数据输出操作
      • cin>>Var; //数据输入操作
    • 复合语句与空语句

      • 复合语句和空语句并不是功能上独立的一类语句。但是,复合语句能够实现语句块的概念
      • 空语句也可以在一些情况下排上用场

        有了复合语句和空语句会使C++程序的设计变得更加方便

选择结构

  • if语句

    • 基本的if语句
    • if…else语句
    • if语句的嵌套

      注意:C++中规定,else关键字总是与它前面最近的未匹配的且课件的那个if关键字配对。

  • switch语句

循环结构

  • for语句
  • while语句
  • do…while语句
  • 循环的嵌套

跳转语句

  • break:只用于循环和switch语句中

  • continue:只用于循环语句。功能:跳转本次循环,继续下一轮循环。

  • return:只在函数体中。

  • goto:跳转到标记的语句位置。

第四章 数组、指针与引用

  • 数组(Array)是由固定数目元素组成的数据结构,同意数组的所有元素的类型都相同。数组元素是通过下标进行访问的,数组可以是一维的,也可以是多为的,许多重要应用的数据节后都是基于数组的。

数组

  • 一维数组

    • 一维数组的定义

      <数据类型><数组名>[<数组长度>]
      
    • 一维数组的初始化

      int v1[] = {1,2,3,4};
      char v2[] = {'a','b','c','d'};
      
    • 访问数组

      <数组名>[<表达式>]
      
  • 多维数组

    • 二维数组的定义

      <数据类型><数组名>[<表达式1>][<表达式2>]
      
    • 二维数组的初始化

      int v1[3][4] = {{1,2,3,4},{1,2,3,4},{1,2,3,4}};
      
    • 访问数组

      <数组名>[<表达式1>][<表达式2>]
      
    • 多维数组
  • 字符数组

    • string类型

      string name = "William Jacob";
      

      在使用string数据类型之前,需要在程序中包含文件string(# include <string>)并声明其所在的名字空间std。

      在C++字符串由零个或多个字符组成,并且所有字符都由双引号括住。

    • 字符数组

      • 所有的元素都是char类型的数组成为字符数组。
      • C++中,空字符‘\0’来表示。
      • 在C++中,空字符(’\0’)用来作为一个字符串结束的标志。

        字符数组与字符串之间存在细微的差别,字符串以空字符结尾,即字符串的最后一个字符总是空字符,而字符数组可以不含空字符

    • 常用字符串函数
    • C++提供了一系列字符串操作的函数,这些函数都包含在头文件cstring中,引用:# include <cstring>

指针

  • 指针(即指针变量)是C++语言最强大的功能之一,同时也是最棘手的功能之一。一个指针是一个特定类型数据的存储地址。

  • 指针的声明形式:

    <数据类型> * <变量名>
    
  • 指针使用两种特殊的运算符—— * 和 &
    • & :一元运算符,用于返回其操作对象的内存地址,其操作对象通常为一个变量。

      C++的引用。&:只能在定义时被赋值,&b=a:表明b与a等价

    • * :与&的作用相反,用于返回其操作数所指对象的值,因此该运算符要求其操作对象为一个指针。

      注意:在使用任何指针变量之前必须先给它赋一个指向合法具体对象的地址值。否则系统会给指针变量赋值一个随机值,该赋值可能会破坏内存中某个地址空间中的内容,严重时将导致程序挂起或机器死机。

    • 使一个指针指向一个具体对象的方法有:
      • 使用new运算符(或malloc和alloc等函数)给指针分配一个具体空间。
      • 将另一个同类型的指针赋给它以获得值。
      • 通过&运算符指向某个对象。
  • 指针运算
    • 指针和整型量可以进行加减
    • 若p1和p2为指针,当p1和p2指向同一个类型时,可以进行赋值。如p1=p2,则p1和p2指向同一个对象。
    • 两个指向同一类型的指针,可进行==、>、<等关系运算,其实就是地址的比较。
    • 两个指向同一数组成员的指针可进行相减,结果为两个指针之间相差元素的个数。

      注意两个指针不能相加

    • 几组常见的指针运算表达式比较
    • p++p+1的区别
      • 指针p++结果为p指向下一个元素
      • p+1结果为下一个元素的指针,但是p本身不变
    • y=*px+1y=*(px+1)的区别
      • *px+1结果取px所指对象内容加1
      • *(px+1)结果为px指针加1。
    • y=*(px)++y=*px+1的区别
      • *(px)++指先取指针px所指对象内容进行运算,然后对指针px所指对象内容加1
      • *px+1 指先取指针px所指对象内容进行运算,然后对指针px加1
  • 指针和数组

    • 在C++中,数组的名字就是指向改数组第一个元素(下标为0)的指针,即该数组第一个元素的地址,也即数组的首地址。
    • 一般情况下,一个数组元素的下标访问a[i]等价于相应的指针访问*(a+i)

      但特别注意的是:数组名和指针(变量)是有区别的,前者是常量,即数组名是一个常量指针,而后者是指针变量。因此尽管可以写成px=a,但不能写成a=px,或a++,或px=&a,因为我们不能改变常量的值,也不能取常量的地址

      数组名可作为参数进行传递。

    • 使用指针的原因
      • 指针运算比数组运算的速度快
      • 使用指针的另外一个原因是在大量数据传递是,传递指针要远比传递数据本身效率高的多,如在函数参数传递及函数返回值时。

        当然,使用指针会给程序带来安全隐患(如指针悬挂问题),同时降低程序的可读性。

引用

  • 引用是个别名,建立时必须用另一个数据对象(如一个变量)的名字进行初始化,以指定该引用所代表的数据对象。此后,对引用的任何操作实际上就是对所代表的数据对象的操作。系统不会为引用再次分配存储空间。

    注意:引用运算符与地址操作符使用的是相同的符号(即运算符加载),但它们的韩不一样,引用运算符只在声明变量的时候使用,它放在类型名后面

  • 使用引用时应遵循一定的规则

    • 创建引用时,必须立即对其进行初始化(指针则可以在任何时候被初始化)

    • 一旦一个引用被初始化为一个对象的引用,它就不能再被改变为对另一个对象的引用(指针则可以在任何时候改变为指向另一个对象)

    • 不可能有NULL引用,必须确保引用是对具体合法的对象的引用(即引用应和一块合法的存储空间共联)

  • 用引用传递函数参数

    • 如果有占用空间大的对象需要作为函数参数传递的时候,在C语言中的做法往往是使用指针,因为这样可以避免将整个实参对象数据全部复制给形式参数,可以提高程序执行效率。而在C++中,由于引入了引用的该男,因此既可以使用指针,亦可以用引用来做同样的事情。

    • 引用真作为参数的最大的好处是既可以想指针那样工作,其实用方式又和一般变量相同。也就是说,引用比指针具有更好的可读性

  • 动态存储分配

    • 静态存储分配:到目前为止,程序中用于存储数据的变量和数组等实体在使用前都必须通过声明语句进行定义。C++编译器根据这些声明语句了解它们所需存储空间大小,并预先为其分配适当的内存空间。也就是说,这些变量或数组在内存中所占据的空间大小必须在编译时确定下来,这种内存分配方式成为“静态存储分配”。
      data++,表示取值 data和data++

    • 动态存储分配:很多情况下,程序中所需要的内存数量只有等到运行时刻才能确定下来,这是就应使用“动态存储分配”的方式申请获得指定大小的内存空间,当动态分配的内存闲置不用时同样有必要对其进行释放。

      • new分配空间
        • 对某种类型变量进行动态分配:<指针> = new <类型>
        • 对数组进行动态分配:<指针> = new <类型> [<元素个数>]

          使用new动态分配的数组与一般定义语句声明的数组之间的最大区别是,前者的元素个数可以是一个变量,而后者的元素个数必须是常量。

      • delete释放空间
        • 单个变量 = delete <指针>
        • 数组 = delete[] <指针> (无参数)

第五章 函数

  • 函数是一个可以独立完成某个功能的语句块。C++程序其实就是由一系列的函数组成,main函数是其中的一个函数,C++程序从main函数开始执行。

  • 函数作用:

    • 将复杂程序拆成若干易于实现的子程序。
    • 将程序中重复出现的功能封装到一个函数中,这样,该功能只需在一个函数中实现,在程序中用到该功能的地方只需要调用该函数即可,这样既提高了程序的开发效率,也提高了程序的可靠性 ,同时也极大地增强了程序的可读性

函数的定义

  • 用户自定义函数形式:

    1
    2
    3
    4
    <返回类型> <函数名>(<形参列表>)
    {
    <函数体>
    }
    • C++中,return是一个关键字,当函数执行到return语句时,函数将立即终止执行,并将程序的控制权返回给调用函数。因此,如果执行到main函数中的return语句时,整个程序将终止。
    • 当一个函数带有返回值时,应保证函数每个可能执行路径上应有返回值。
    • return语句的第二种形式用于无返回值的函数,即函数返回类型为void。此时函数进将程序控制返回给调用函数,并不返回一个值。当函数没有return语句时,在执行完最后一条语句后将返回到调用函数。

函数调用

  • 调用的一般形式:

    1
    <函数名>(<实参表>)
  • 函数的调用方法分为:

    • 语句调用:通常是不带返回值的函数。

    • 表达式调用:将被调用的函数作为表达式的一部分进行调用,适用于被调用函数带有返回值的情况。

    • 参数调用:被调用函数作为另个一函数的一个参数进行调用。

函数原型

  • 在C++中,函数使用之前要预先声明,这种声明在标准C++中称为函数原型。

  • 函数原型的语法:

    1
    <返回类型> <函数名>(<形参列表>);
  • 函数原型声明的两种形式

    • 直接使用函数定义的头部,并在后面加上一个分号。

    • 在函数原型声明中省略参数列表中的形参变量名,仅给出函数名、函数类型、参数个数及次序。如int max (int int);

      C++中,调用任何函数之前,必须保证它已有原型声明。函数原型通常放在程序文件的头部,以使得该文件中所有函数都能调用它们。实际上,标准函数的原型声明放在了相应的头文件中

函数返回类型

根据函数是否带有参数以及函数是否有返回值,可以将函数分为如下四类:

  • 带参数的有返回值的函数

  • 不带参数的有返回值函数

  • 带参数的无返回值函数

  • 不带参数的无返回值函数

函数参数

  • 参数的传递方式

    • 传值:将实参的副本传递(拷贝)给被调用函数的形参,不改变原值。

    • 传地址:改变原值

      • 有时需要函数调用来改变实参变量的值,或通过函数调用返回多个值,这时仅靠传值方式是不能达到目的的。

      • 传指针属于显式传递地址,因此尽管传递指针可以达到目的,但传递方式略显笨拙,而且也不直观。

      • 函数引用参数仅在函数定义时说明,而带引用参数的函数的调用形式与值参数相同。

    • C++中,当函数参数需要传递地址时,建议使用引用来替代指针,因为引用比指针更加直观。

    • 引用的适用情形:
      • 要从函数中返回多个值
      • 通过函数调用要改动实参值
      • 传递地址可以洁身拷贝大量数据所需的内存空间和时间
  • 默认参数

    • C++中,可以为形参指定默认值,在函数调用时没有指定与形参相对应的实参时就自动使用默认值。默认参数可以简化复杂函数的调用。

    • 如果一个函数中有多个参数,则默认参数应从右至左逐个定义。

      C++引入默认参数使得程序员能够处理更为复杂的问题。使用默认参数,程序员只需记住针对确切情形有意义的参数,不需要指定常见情况下使用的参数。

函数重载

  • 在C中,编写函数时必须确保函数名唯一。但是C++中可以共用相同的函数名。

  • 函数重载:用同一个函数名字在不同类型上做相类似的操作就会方便很多,这种情况较函数重载。

  • C++根据所传递的实参的不同,调用相应的函数u,得到期望的效果。

    C++中的函数重载中,如果函数知识函数类型不同,而其他(参数个数及类型)完全相同,则不能作为重载函数来使用。

注意:让重载函数执行不同的功能是非常不好的程序设计风格,同名函数应该具有相同的功能。

内联函数

  • C++引入内联函数的原因是用它来取代C中的预处理宏函数
  • 内联函数与宏函数的区别:

    • 宏函数是有预处理器对宏进行替换
    • 内联函数是通过编译器实现的
  • 内联函数是真正的函数,只有在调用的时候,内联函数像宏函数一样展开,所以它没有一般函数的参数压栈和退栈操作,减少了调用开销,因此内联函数比普通函数执行效率更高。

  • C++中,使用inline关键字来定义内联函数,inline关键字放在函数定义(声明)中函数类型之前。

    编译器会将在类的说明部分定义的任何函数都认定为内联函数,即使它们没有用inline说明。

  • 注意

    • 内联函数使用方式和一般函数一样,只不过在程序执行时并不产生实际函数调用,而是在函数调用处将函数代码展开执行。

    • 内联函数有一定的局限性,就是函数中的执行代码不能太多,结构也不能太复杂。如果内联函数的函数体过大,编译器将会放弃内联方式,而是采用普通的方式调用函数。

递归函数

  • 如果一个函数在其函数体内,直接或者间接地调用了自己,该函数就被称为递归函数。递归是解决某些复杂问题的十分有效的方法。

  • 递归适用的场合

    • 数据的定义形式按递归定义。

    • 数据之间的关系(即数据结构)按递归定义的,如树的遍历,图的搜索等。

    • 问题解决按递归算法实现的,例如回溯法。

  • 使用递归需要注意以下几点

    • 用递归编写代码往往较为简洁,但要牺牲一定的效率,因为系统处理递归函数时都是通过压栈/退栈的方式实现的。

    • 无论哪种递归调用,都必须有递归出口,即结束递归调用的条件。

    • 编写递归函数时需要进行递归分析,既要保证正确使用了递归语句,还要保证完成了相应的操作。

变量的生存周期

  • 全局变量:变量由编译程序在编译时给其分配存储空间(称为静态存储分配),并在程序执行过程中始终存在。这类变量的生存周期与程序的运行周期相同。

  • 局部变量:变量由程序运行时自动给其分配存储空间(称为动态存储分配),这类变量为函数(或块)中定义的自动变量。这类变量的生存周期与函数(或块)的执行周期相同。

由于作用域的屏蔽效应,如果函数中有同名变量,则不能访问外部变量。为了能在函数内部访问外定义的变量,可以使用C++中的作用域运算符::

当程序较大时,利用名字评比机制是非常必要的,但是这也会导致程序的可读性变差,好的程序设计风格应尽量避免名字屏蔽。

第六章 类和对象

类是面向对象程序设计的核心,通过抽象数据类型方法实现的一种用户自定义数据类型,它包含了数据和对数据进行操作的函数,利用类可以实现数据的封装和隐藏。

类的定义

  • 类是一种由用户定义的复杂数据类型,它是将不同类型的数据和与这些数据相关的操作封装在一起的集合体。C++中类概念的目标就是为程序员提供一种建立新类型的工具,使这些新类型的使用能够像内部数据类型一样方便。
  • 类定义的一般格式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 类的说明部分
    class <类名>
    {
    public:
    <成员函数或数据成员的说明> \ //公有成员,外部接口
    protected:
    <数据成员或成员函数的说明> \ //保护成员
    private:
    <数据成员或成员函数的说明> \ //私有成员
    };
    // 类的实现部分
    // <各个成员函数的实现>
    • 花括号表示类的声明范围,其后的分号表示类申明结束。

    • 类的成员包括数据成员和成员函数,分别描述类所表达的问题的属性和行为。

    • 关键字public、private和protected成为访问权限修饰符,它们限制了类成员的访问控制范围。

    • 各个成员函数的实现既可以在类体内定义,也可以在类体外定义。

      如果一个成员函数在类体内进行了定义,它将不出现类的实现部分;如果所有的成员函数都在类体内进行了定义,则可以省略类的实现部分。在类体内定义的成员函数都是内联函数。

  • 类成员的访问控制

    • 类中的成员具有不同的访问权限修饰符。这些不同的访问权限修饰符限制了类成员的不同访问控制范围。
    • 三种访问控制权限:

      • 公有(public):定义类的外部接口,任何来自类外部的访问都必须通过外部接口进行。

      • 私有(private):私有类型的成员只允许本类的成员函数访问,来自类外部的访问都是非法的。

      • 保护(protected):介于共有类型和又有类型之间,在继承 和派生时可以体现其特点。

        类中成员默认的访问权限是私有的(private)。

  • 类的数据成员

    • 类中的数据成员描述类所表达的问题的属性。数据成员在类体中进行定义,其定义方式与一般变量相同,但对数据成员的访问要收到访问权限修饰符的控制。
    • 定义了类的数据成员时,注意的问题:

      • 类中的数据成员可以是任意类型,包括整型、浮点型、字符型、数组、指针和引用等,也可以是对象。

        但是要注意的是,只有其他类的对象,才可以作为该类的成员,即作为该类的成员对象而存在。自身类的对象是不可以作为自身类的成员存在的,但自身类的指针可以。

      • 在类体中不允许对所定义的数据成员进行初始化。

  • 类的成员函数

    • 类的成员函数描述类所表达的问题的行为。类中所有的成员函数都必须在类体内进行说明。但成员函数的定义既可以在类体中给出,也可以在类体外给出。

    • 在类外部对成员函数进行定义,一般格式:

      1
      2
      3
      4
      <返回类型> <类名>::<成员函数名>(<参数表>)
      {
      <函数体>
      }
    • 成员函数的两种定义方式的差别:

      • 如果一个成员函数的声明和定义都在类体内,这个成员函数就是内联函数。
      • 如果一个成员函数的声明在类体内,而定义在类外,这时对该成员函数的调用时按一般函数进行的。

        若要将定义在类体外的成员函数也作为内联函数处理,就必须在成员函数的定义前加上关键字inline,以此显式地说明该成员函数也是一个内联函数。

    • 成员函数除了可以定义为内联函数意外,也可以进行重载,可以对其形参设置默认值。

对象的定义

  • 对象是类的实例,一个对象必须属于一个已知的类。因此定义对象之前,必须先定义该对象所属的类。

  • 对象的定义格式如下:

    1
    <类名> <对象名>(<参数表>);
    • <类名>:待定义的对象所述的类的名字。

    • <对象名>:可以是一个或多个对象名,多个对象名之间用逗号隔开。

    • <参数表>:初始化对象时需要的,建立对象时可以根据给定的参数调用相应的构造函数对对象进行初始化。无参数时表示调用类的默认构造函数。

      除了定义一般对象外,还可以定义对象数组、指向对象的指针或引用。

  • 对象的成员

    • 一个对象的成员就是该对象的类所定义的成员,包括数据成员成员函数。定义对象后,可以使用“.”运算符和“->”运算符访问对象的成员。

    • “.”运算符,适用于一般对象和引用对象。

    • “->”运算符,适用与指针对象(即指向对象的指针)。

      实际上,一般对象成员与指针对象成员的表示方法只是形式上有所不同,本质上是相同的。

构造函数和析构函数

  • 构造函数与析构函数的定义

    • 构造函数的作用:在对象被创建时利用特定的值构造对象,将对象初始化为一种特定的状态(对数据成员进行初始化),使该对象具有区别于其他对象的特征。

      构造函数在对象被创建时有系统自动调用。

    • 构造函数也是类的成员函数,但是它是一种特殊的成员函数,它除了具有一般成员函数的特征之外,还具有一些特殊的性质:

      • 构造函数的名字必须与类名相同。
      • 构造函数不指定返回类型,它隐含有返回值,由系统内部使用。
      • 构造函数可以有一个或多个参数,因此构造函数可以重载。
      • 在创建对象时,系统会自动调用构造函数。
    • 构造函数的格式:

      1
      2
      3
      4
      5
      6
      <类名>::<构造函数名>(<形参表>):(<初始化列表>);
      // 例如
      Date::Date(int y,int m,imt d):year(y),month(m),day(d)
      {
      <构造函数体>
      }
      • 初始化列表:冒号后面是一个构造函数的初始化列表,用于初始化类中的各个数据成员。初始化列表位于构造函数的形参表后面,有一个冒号和逗号分割的若干项构成。初始化列表形式为:成员名(表达式)构成。
      • 在构造函数中,初始化优先于赋值。在调用构造函数对类对象初始化时,先执行初始化列表对各个成员进行初始化,再执行构造函数体。
      • 初始化顺序:初始化列表中各个初始化项的执行顺序取决于类成员在类中声明的顺序,而与初始化列表中给出的初始化项的顺序无关.
      • 数据成员初始化方式:既可以使用初始化列表的方式获得显式初值,也可以在获得默认初值后,再在构造函数体中使用赋值语句将表达式的值负值给数据成员。
    • 析构函数与构造函数的作用几乎正好相反,它用来完成对象被删除前的以前清理工作,也就是专门做扫尾工作的。一般情况下,析构函数在对象的生存周期即将结束的时候由系统自动调用。它的调用完成之后,对象也就消失了,相应的内存空间也就被释放。

    • 析构函数也是类中的一种成员函数,具有以下特性:

      • 析构函数名是在类名前加一个取反(求补)符号~。
      • 析构函数不指定返回类型,它隐含有返回值,由系统内部使用。
      • 析构函数没有参数,因此析构函数不能重载,一个类中只能定义一个析构函数。
      • 在撤销对象时,系统会自动调用析构函数。

      注意析构函数的调用顺序与构造函数的调用顺序是完全相反的。

  • 默认构造函数和默认析构函数

    • 默认构造函数就是调用时不必提供参数的构造函数,它的函数名与类名相同,它的参数表或者为空,或者它所有参数都具有默认值。

    • 如果类中定义了一个默认的构造则使用该函数,如果一个类中没有定义任何构造函数,编译器将会生成一个不带参数的公有默认构造函数,它的定义格式:<类名>::<类名>(){},同理公有的默认析构函数格式是:<类名>::~<类名>(){}

  • 复制构造函数

    • 复制构造函数的功能:用一个已知的对象去初始化一个正在创建的对象。

    • 类中还有一种特殊的构造函数叫做复制构造函数,它用一个已知的对象初始化出一个正在创建的同类对象,复制构造函数的一般格式如下:

      1
      2
      3
      4
      <类名>::<类名>(const<类名>& <引用对象名>)
      {
      //复制构造函数体
      }
    • 复制构造函数具有的特点:

      • 也是一种构造函数,因此函数名与类名相同,并且不能指定函数返回类型。

      • 只有一个参数,是对同类的某个对象的引用。

      • 每一个类中都必须有一个复制构造函数。如果类中没有声明复制构造函数,编译器会自动生成一个具有上述形式的公有复制构造函数。

    • 通常情况下,复制构造函数在下面情况下回被调用:

      • 用类的一个已知的对象去初始化该类的另一个正在被创建的对象。

      • 采用传值调用方式时,对象作为函数实参传递给函数形参。

      • 对象作为函数返回值。

自由存储对象

  • 定义:到目前为止,在为程序中定义的对象分配内存空间时采用的都是“静态存储方式”,在编译时就能确定所占存储空间的大小,而与之相对的动态存储分配技术则可以保证在程序运行过程中按照实际需要申请适量的内存,使用结束后在进行释放。这种程序运行过程中根据需要可以随时建立或删除的对象成为自由存储对象。建立和删除工作分别由运算符newdelete完成。

  • new创建单个对象时,要根据参数调用相应的构造函数,在用new创建对象数组时,会调用默认构造函数,用delete删除对象时,要调用析构函数。

在对自由存储对象调用构造函数和析构函数时,要注意,这时的调用时显式进行的,调用的顺序取决于newdelete运算符的顺序。这时析构函数的执行顺序不一定与构造函数的执行顺序严格相反,但对象数组中各个元素的构造和析构顺序仍然是相反的。

this指针

  • C++中提供一种特殊的对象指针——this指针,它是成员函数所属对象的指针,它指向类对象的地址。成员函数可以通过这个指针知道自己属于哪一个对象。

  • this是一个隐含的指针,它隐含于每一个类的非静态成员函数中,它明确地表示出了成员函数当前操作的数据所属的对象。当一个对象调用成员函数时,编译程序先将对象的地址赋值给this指针,然后调用成员函数。每次成员函数存取数据成员时,会隐含使用this指针。

静态成员

  • 对于类中的非静态成员,每一个类对象都拥有一个副本,即每个对象的同名数据成员可以分别存储不同的数值,这是保证每个对象拥有区别于其他对象的特征的需求。

  • 类中的静态成员则是解决同一个类不同对象之间的数据和函数共享问题的。静态成员的特性是不管这个类创建了多少个对象,它都只有一个副本,这个副本由所有属于这个类的对象共享。静态成员表示整个类范围的信息,其声明以static关键字开始,包括静态数据成员和静态成员函数

  • 静态数据成员

    • 示例

      1
      2
      3
      4
      5
      6
      class MyClass
      {
      private:
      int a,b,c;
      static int sum; //私有静态数据成员
      };
    • 说明

      • sum是静态数据成员,它被MyClass类的所有对象共享,但它不属于MyClass类的任何一个对象,它的作用域类范围

      • 静态数据成员在每个类对象中并不占有存储空间,它只是在每个类中分配有存储空间,供所有对象公用。静态数据成员的值对每一个对象都是一样的

    • 初始化:静态数据成员具有静态生存周期,必须对它进行初始化。静态数据成员初始化的一般格式如下。

      1
      <数据类型> <类名>::<静态数据成员名>=<初始值>

      对静态数据成员初始化时注意:

      • 由于在类的声明中仅仅是对静态数据成员进行了引用性声明,因此必须在文件作用域的某个地方对静态数据成员进行定义并初始化,即应在类体外对静态数据成员进行初始化(静态数据成员的初始化与它的访问控制权限无关)。
      • 静态数据成员初始化时前面不需要加static关键字,以免与一般静态变量或对象混淆。
      • 由于静态数据成员是类的成员,因此在初始化时必须使用作用域运算符(::)限定它所属的类。
  • 静态成员函数

    • 公有的静态成员可以直接访问,但是私有的或保护的静态数据成员却必须通过公用的接口进行访问,一般将这个公用的接口定义为静态成员函数。

    • 使用static关键字声明的成员函数就是静态成员函数,静态成员函数也是属于整个类而不属于类中的某个对象,它是该类的所有对象共享的成员函数。

    • 静态成员函数既可以在类体内进行定义,也可以在类体外定义。当在类体外定义时,要注意不能使用static关键字作为前缀

    • 由于静态成员函数在类中只有一个副本,因此它访问对象的成员时收到一些限制静态成员函数可以直接访问类中说明的静态成员,但不能直接访问类中说明的非静态成员;若要访问非静态成员时,必须通过参数传递的方式得到相应的对象,在通过对象进行访问。

    • 公有的静态成员既可以直接使用作用域运算符通过类名进行访问,也可以通过类的任何对象进行访问,但是建议使用前者访问,即:

      1
      2
      3
      <类名>::<静态数据成员名>

      <类名>::<静态数据成员名>(<参数表>)

      由于静态成员在该类的任何对象被建立之前就存在,因此静态成员可以在程序内部不依赖于任何对象被访问,即使没有建立该类的任何一个对象时也可以使用作用域运算符通过类名访问类的共有静态成员。

常成员

  • 存在的意义:虽然数据隐藏保证了数据的安全性,但各种形式的数据共享却又不同程度地破坏了数据的安全性。因此,对于既需要共享又需要防止改变的数据应该定义为常量进行保护,以保证它在整个程序运行期间是不可改变的。

  • 这些常量需要使用const修饰符进行定义。const关键字不仅可以修饰类对象本身,也可以修饰类对象的成员函数和数据成员,分别称为常对象、常成员函数和常数据成员。

  • 常对象

    • 使用const关键字修饰符的对象称为常对象,其定义格式如下:

      1
      2
      3
      <类名> const <对象名>

      const <类名> <对象名>

      常对象在定义时必须进行初始化,而且不能被更新。

  • 常成员函数

    • 使用const关键字说明的成员函数被称为常成员函数,常成员函数的说明格式如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      		<返回类型> <成员函数名>(<参数表>) const;
      ```
      - const是函数类型的一个组成部分,因此在函数实现部分也要带有const关键字;如果在定义常成员函数时丢失了const关键字,程序会产生错误。

      - 常成员函数也不能更新对象的数据成员,否则也会产生错误。

      - 当成员函数时常成员函数时,常对象和一般对象都是可以调用它的,但是对于一般成员函数,则只有一般对象可以调用,常对象不能调用一般成员函数,如果调用它将会产生错误。
      - const关键字可以用于参与对重载函数的区分。重载的原则是:常对象调用常成员函数,一般对象调用一般成员函数。此处需要注意的是:当类中只有一个常成员函数时,一般对象也可以调用该常成员函数。但当两个同名的一般成员函数和常成员函数同时存在时,遵循上述重载原则。

      - 常数据成员
      - 使用const说明的数据成员称为常数据成员。常数据成员的定义与一般常量的定义方式相同,只是它的定义必须出现在类体内。

      - 常数据成员同样也必须进行初始化,并且不能被更新。但常数据成员的初始化只能通过**构造函数**的成员初始化列表显示进行。

      ## 友元
      - 存在的意义:类具有数据**封装和隐藏**的特性,只有类的成员函数才能访问类的私有成员和保护成员,外部函数只能访问类的公有成员。但是在某些情况下,需要在类的外部访问类的私有成员和保护成员。这时,如果通过公有成员函数进行访问,由于参数传递、类型检查和安全性检查等需要时间上的开销,并影响程序的**运行效率**。为了解决这个问题,引入友元。

      - **友元:可以在类外部直接访问类的私有成员和保护成员,提高程序的运行效率。**

      - 友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。对于一个类,可以利用`friend`关键字将一般函数、其他类的成员函数或者是其他类声明为该类的友元,使得这个类本来隐藏的信息(包括私有成员或保护成员)可以被友元所访问。

      - 如果友元是一般成员函数或者类的成员函数,称为友元函数;如果友元是一个类,则成为友元类,友元类的所有成员函数都被称为友元函数。


      - **友元函数**
      - 友元函数不是当前类的成员函数,而是**独立于当前类的外部函数**(包括普通函数和其他类的成员函数),但它可以访问该类的所有成员,包括私有成员、保护成员和公有成员。
      - 友元函数要在类定义时声明,声明时要在其函数名前加上**关键字friend**。 该声明可以放在**公有部分,也可以放在私有部分和保护部分**。友元函数的定义通常在类外部。

      > 例子

      #include
      using namespace std;

      class DATE{
      public:

      DATE(int y=2003,int m=1,int d=1):year(y),month(m),day(d){ }
      friend void DateTime(const DATE &d,const TIME &t);
      

      private:

      int year,month,day;
      

      };

      void DateTime(const DATE &d){
      cout<<”Now is:”<<d.year<<”.”<<d.month<<”.”<<d.day<<”.”<<endl;
      }

      1
      2
      3
      4
      5
      6
      7
      8
      	
      > 由于采用friend将外部函数DateTime设置为DATE类友元函数,所以这个函数能够直接访问类的成员变量。

      - **友元类**

      友元类除了可以是函数外,还可以是类,即一个类可以作为另一个类的友元,称为友元类。友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类的所有成员函数和成员变量。

      友元类的说明方法:

      friend <类名> // 友元类类名

      1
      2
      3
      4
      5
      6
      7


      ## 对象数组

      - **定义**:对象数组是指数组元素为对象的数组,该数组的每一个元素都是同一个类的对象。

      - 对象数组定义的格式如下:

      <类名> <数组名>[<大小>]…

      1
      2

      - 使用对象数组成员的一般格式:

      <数组名>[<下标>].<成员名>
      `

成员对象

  • 定义:类的数据成员可以是简单类型或自定义类型的变量,也可以是类类型的对象。因此,可以利用已定义的类来构造新的类,使得一些复杂的类可以有一些简单的类组合而成。当类的数据成员为其他类的对象时,这个对象被称为成员对象。

  • 初始化,当类中出现了成员对象时,该类的构造函数要包含对成员对象的初始化,成员对象的初始化工作也是在成员初始化列表中完成的。

  • 构造顺序:建了一个类的对象时,要调用它的构造函数对类对象初始化,此时应先执行初始化列表,对各个成员进行初始化,再执行当前类的构造函数体。如果类中包含有成员对象,注意要根据初始化的参数调用成员对象的构造函数对其进行初始化。

  • 成员对象初始化时,根据初始化列表的特点可知,类中有多个成员对象时,要按照定义成员对象的顺序建立各个子对象(即成员对象构造函数的执行顺序仅与成员对象在类中声明的顺序有关,而与成员初始化列表中给出的成员对象的顺序无关)。如果在构造函数的成员初始化列表中没有给出对成员对象的初始化,则表示使用成员对象的默认构造函数,如果成员对象所在的类没有默认构造函数,将产生错误。

第七章 继承和派生

概念

继承是面向对象程序设计的一个重要特性,是软件复用的一种形式,它允许在原有类的基础上创建新的类。新类可以从一个或多个原有类中继承函数和数据,并且可以重新定义或增加新的数据和函数,从而形成类的层次或等级。

派生类与基类成员的访问

派生类的构造函数和析构函数

多继承和虚基类

*子类型关系

虚函数与多态性

第八章 运算符重载

运算符函数与运算符重载

典型运算符的重载

*运算符重载应注意的几个问题

第九章 模板

函数模板

类模板

第十章 C++流

C++流的概念

输入输出的格式控制

文件流