Skip to content

lerenhua/c_Primer_Plus_exercise

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

c_Primer_Plus_exercise

C++ Primer Plus 第五版编程练习题集合
此repo用于记录书本课后编程习题代码,记录自己的学习进度

目录

章节内容概述

chapter 2

  • C++程序的一般形式:
    * 注释, 由//前缀指示。
    * 预处理器编译指令 #include。
    * 函数头 int main() 。
    * 编译指令,如using namespace。
    * 函数体,使用{}括起。
    * 结束 main 函数的返回语句。

  • C++基本输入输出方法:cin ,cout

  • 名称空间与类的简介

  • C++语句
    * 声明语句及变量。
    * 赋值语句。
    * 输入输出语句。

  • 函数
    1. 使用有返回值的函数。
    2. 函数变体 -- 某些语言中,有返回值函数为函数(function),无返回值函数为过程(procedure)或子程序(subroutine),在C++中两种变体统称函数。
    3. 用户定义的函数:3.1 函数原型; 3.2 函数定义

chapter 3

  • 变量命名规则
    1. 只能使用字母,数字和下划线(_)。
    2. 名称第一个不能是数字。
    3. 区分大小写。
    4. 不能使用C++关键字作名称。
    5. 以两个下划线或下划线和大写字母开头的名称被保留给实现(编译器及其使用的资源)使用。以一个下划线开头的名称保留给实现,用作全局标识符。(此处合法但是可能出现问题)
    6. C++对名称长度无限制。

  • C++内置整型
    1. unsigned long , long , unsigned int , int , unsigned short , short
    2. char , unsigned char , signed char , bool
    3. C++中short, int, long的最小长度标准:
      3.1 short至少16位。
      3.2 int至少和short一样长。
      3.3 long至少32位,且至少与int一样长。(具体整型长度参阅头文件climits)
    4. char类型:专门用于存储字符及小数
    5. signed char 与 unsigned char: 默认情况下,char本身既不是没有符号也不是有符号,而依赖于C++实现,如果char作数值类型使用时,可显式确定有无符号。
    6. wcha_t类型:用于解决char类型字长较小无法表示更大字符集的宽字符类型,其底层类型的选择取决于实现,如一个系统可能以unsigned short实现而另一系统为int;由于cin,cout输入输出只适用于char流,因此需要wcin和wcout实现wcha_t流输入输出;另外可以通过加前缀‘L’来指示宽字符常量及宽字符串。
    7. bool类型: 非零值为true,零值为false;任何数值或指针值都可以隐式转换成bool值。

  • const限定符
    * 用于声明符号常量。
    * 相对于#define,推荐使用const定义符号常量。

  • C++内置浮点类型
    1. float, double, long double
    2. 位数要求:float至少32位,double至少48位且不少于float,long double至少和double一样多。(实际上,通常float 32位,double 64位,long double 为80,96,128位)

  • C++算术操作符
    * 加(+),减(-),乘(*),除(/), 取模求余数(%)。
    * %只用于整数。

  • 类型转换
    1. 类型自动转换的情况:
      * 将一种算术类型的值赋值给另一种算术类型的变量时,自动转换。
      * 表达式中包含不同类型值时,自动转换。
      * 将参数传给函数时,自动转换。
    2. 强制类型转换
      * C++风格形式:typeName(value)
      * 强制类型转换不改变变量本身,而是创建新的指定类型的值。

chapter 4

数组

  • 继承于C语言的数组,使用方法一致
  • 声明语句要素:
    1. 存储在每个元素的值的类型
    2. 数组名
    3. 数组中的元素个数 (特殊情况:定义与初始化一起时可不指定个数)
  • 初始化规则
    1. int cards[4] = {3, 6, 8, 10};(合法)
    2. int hand[4];(合法)
    3. long totals[500] = {5, 2};(部分初始化,其余为0;合法)
    4. short things[] = {1, 5, 3, 8};(可不指明个数初始化,编译时编译器会计算出个数)
  • 注意
    1. 使用数组时,要使用有效下标值。因为编译器不会检查下标是否有效或越界。
    2. 只有定义数组时才能初始化,之后无法使用数组赋值数组的方式

字符串

C-风格字符串(c-style string)
  • C-风格字符串性质:以空字符(null character)结尾,空字符被写作\0,用以标记字符串结尾,如 char dog[5] = {'b', 'e', 'a', 'u', 'x'} 非字符串, char cat[5] = {'f', 'a', 't', 's', '\0'} 为字符串。
  • 字符串常量(使用双引号)与字符常量(使用单引号)不能互换。
  • 拼接字符串常量:
    • 任何两个由空白符(空格,制表符,换行符)分隔的字符串常量自动拼接,如cout << "I'd give my right arm to be" "a great violinist.\n"; 等价 cout << "I'd give my right arm to be a great violinist.\n";
  • 在数组中使用字符串
  • 字符串输入
    • cin在接收输入时只读取一个单词,并以空白来界定字符串的界。
    • 输入的字符串长度可能大于数组长度
  • 每次读取一行字符串输入
    1. 面向行的输入:cin.getline(arrayName, size)
    2. 面向行的输入:cin.get(arrayName, size)
    3. 注意: getline()通过换行符确定结尾,并舍弃换行符; 而 get()会保留换行符在队列中,因此
    cin.get(name, size);
    cin.get(dessert, size);
    连续调用get()时第二个无法读取内容。

string类简介

  • string类与字符数组相同之处:
    • 可以使用c-风格字符串初始化string对象。
    • 可以使用cin键盘输入存储到string对象。
    • 可以使用cout显示string对象。
    • 可以使用数组表示法来访问存储在string对象中的字符。
  • 赋值,拼接和附加
    string str1, str2, str3;
    str3 = str1 + str2;
    str3 += str2;

结构简介

  • 定义结构描述
    struct inflatable
    {
      char name[20];
      float volume;
      double price;
    }
    列表中每一项为结构成员,因此,inflatable结构有3个成员。
  • 创建结构变量
    inflatable hat;
    struct inflatable goose;
    关键字struct可省略。hat类型为inflatable,可以使用成员操作符(.)来访问各个成员。
  • 结构数组
    //可创建元素为结构的数组
    inflatable gifts[100]; // 声明包含100个inflatable结构的数组
    inflatable guests[2] = {
      {"Bambi", 0.5, 21.99}, 
      {"Godzilla", 2000, 565.99}
    };  // 声明2个包含inflatable结构的数组,并初始化
  • 定义好的结构,可理解为一种新数据类型,与int,float等类型概念相同,因此可以直接默认为数据类型。

共用体

  • 共用体(union)是一种数据格式,能存储不同的数据类型,但同时只能存储一种类型。与结构体不同,结构体可同时存储多种类型。
  • 声明及使用:
    union one4all
    {
      int int_val;
      long long_val;
      double double_val;
    };  // one4all可以存储多种类型,但一次只能存储一种类型的值
    one4all pail;
    pail.int_val = 15;  // 存储了一个int值
    cout << pail.int_val;
    pail.double_val = 1.38;  // 存储了一个double值,同时丢失了int值
    cout << pail.double_val;
  • 共用体长度为其最大成员长度
  • 当数据项使用两种或更多格式(但不会同时使用)时,可使用共用体节省空间。
  • 匿名共用体没有名称,其成员将成为位于相同地址处的变量,每次只有一个成员是当前成员:
    struct widget
    {
      char brand[20];
      int type;
      union   // 匿名共用体
      {
        long id_num;
        char id_char[20];
      };
    };
    widget prize;
    if (prize.type == 1)  // id_num和id_char被认为是prize的两个成员,具有相同的地址,由程序员确定使用哪个成员
      cin >> prize.id_num;
    else
      cin >> prize.id_char

枚举

  • enum 用于创建符号常量的方式,可代替const

指针和自由存储空间

  • 对于常规变量,其存储的是值,使用地址操作符(&)获取地址;指针是一种特殊数据类型的变量,其存储的是地址,使用接除引用操作符(*)获取对应地址存储的值。
  • 声明与初始化指针
    int *ptr; // c-风格指针声明
    int* ptr; // c++风格指针声明,认为int*为一种类型
    int* p1,p2; // p1为指向int的指针变量,p2为int类型常规变量,每一个指针变量都要有一个 *
    int higgen = 5;
    int * pt = &higgen;  // 指针也是一种数据类型的变量,其存储的值为地址,因此初始化传递的应是地址
    
    long * fellow;
    *fellow = 223323; // 警告!!! 一定要在对指针应用解除引用操作符(*)之前,将指针初始化为一个确定,适当的地址!!
    
    int * pt;
    pt = (int *)OxB8000000;  // 数字不能直接传递给指针,需要强制类型转换成地址类型 !!
  • newdelete
    • 使用new分配内存
    typeName pointer_name = new typeName;  // 数据类型可以是结构,或基本类型
    int * pn = new int;  // new会自动分配内存,并返回内存块的地址
    • 使用delete释放内存
    int * pt = new int;
    delete pt;  // 一定要配对使用new及delete,只能用于释放new分配的内存
    
    int * ps = new int;
    int * pq = ps;
    delete pq;  // delete使用关键在于,释放new分配的内存,即使用new时返回的内存块的地址,此地址也存储在指针pq中,因此可以对指针pq使用delete。警告!!!一般不要使两个指针指向同一内存块
    • 使用new创建动态数组
      1. 创建动态数组
      int * psome = new int [10];  // new返回第一个元素的地址,并赋给指针psome
      delete [] psome;  // 释放内存
      1. 使用动态数组
      • 直接将指针当作数组名使用即可,此处数组与指针基本等价
      • 可对指针进行加减操作,加一即指向下一个元素的地址,而数组名不可改变
      int * p1 = new int [3];
      p1[0] = 2;p1[1] = 5;p1[2] = 8;
      p1 = p1 + 1;  // 此时p1[0] = 5
      1. newdelete使用规则
      • 不要使用delete释放不是new分配的内存
      • 不要使用delete释放同一内存块两次
      • 如果使用new [] 为数组分配内存,则应使用delete []来释放
      • 如果使用new [] 为一个实体分配内存,则应使用delete (没有方括号)来释放
      • 对空指针应用delete是安全的

指针,数组和指针算术

  • 指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式。整数常量加1,其值将加1;而指针加1,增加的量为它指向的类型的字节数(如指向int整型的指针加1,其增加的量为int整型对应的字节数,在计算机系统内存中,地址是以字节为单位递增的)。这也说明C++将数组名解释为地址。

  • 多数情况下,C++将数组名解释为第一个元素的地址。

    double wages[10];
    double * pw = wages;
    double * pw0 = &wages[0];  // 数组名即为第一个元素的地址

    使用数组表示法时,C++都将执行以下转换: arrayname[i] 变成 *(arrayname + 1)

  • 指针与数组区别

    • 指针的值可修改,数组名不可修改
    • 对数组使用sizeof()时,返回的是数组长度(以字节为单位,数据类型对应字节X数组元素个数);而对指针使用sizeof()使,返回的是指针的长度。这种情况下,不会将数组名解释为地址!!
  • 指针与字符串

    • 在cout和多数C++表达式中,char数组名指向char的指针以及用双引号括起来的字符串常量都被解释为字符串的第一个字符的地址。因此在使用cout时输出字符串,实际是接收字符串的第一个字符地址,然后不断打印直到检测到\0字符串结束符。
      const char * p = "string";  // 合法,可以使用指针访问字符串
      char * p = "string"; // 虽然编译通过,但实际禁止,不允许这种方式初始化指针!!
      
      const string * p = "ABCD";
      string * p = "ABCD";  // 这两种初始化指针方式都不对,因为字符串常量本身并不是string对象
      
      string str = "ABCD";
      string * p = &str;  // 此种指针指向string对象
    • 注意:不要使用字符串常量或未初始化的指针接收输入!
  • new用于结构:创建结构及访问成员

    struct inflatable
    {
      int price;
      ...
    };  // 如之前一样,先定义结构类型inflatable
    inflatable * ps = new inflatable;  // 创建一个未命名的inflatable类型,并将其地址赋给一个指针
    ps -> price;  // 可用于访问结构的price成员
    (*ps).price;  // 同样使用解除引用操作符(*)加(.)访问成员
  • C++管理数据内存的方式

    1. 自动存储
      在函数内部定义的常规变量使用自动存储,随着函数的调用自动产生,随着函数的结束自动释放。实际上,自动变量是一个局部变量。

    2. 静态存储
      使变量成为静态的方式:在函数外定义;使用关键字static声明变量.

    3. 动态存储
      使用newdelete。使得数据的生命周期不受函数或程序生存时间控制。

chapter 5

for循环

  • for循环语句:
    for (initialization; test-expression; update-expression)
      {
        body
      }
  • for循环执行步骤:
    1. 设置初始值。(初始部分可声明变量)
    2. 执行测试表达式,查看循环是否要继续进行,为真继续进行循环。
    3. 执行循环体操作。
    4. 更新用于测试的值。
  • 表达式与语句:
    • 在C++中,每个表达式都有值。有时很明显,如22 + 27其值为49;有时不明显,如x = 20,赋值表达式也有值,其值为20。
    • C++表达式是值或值与操作符的组合。
    • 副作用(size effect):
      判定表达式的值会改变内存中的数据的值时,称表达式有 副作用。如 x + 15会计算一个新的值,但不会改变x的值。不过对于 ++x + 15时,表达式就有副作用,因为x的值发送了改变。
    • 表达式加分号就是C++有效的语句。
  • 非表达式和语句:返回语句,声明语句和for语句都不满足“语句=表达式+分号”的格式。
  • 递增操作符(++)和递减操作符(--)
    • 前缀版本:++b,表示先将b的值加1,然后使用新的值来计算表达式。
    • 后缀版本:a++,表示使用a当前的值计算表达式,然后再将a的值加1。
    int x = 5;
    int y = ++x;  // 改变了x,然后赋值给y。x为6,y为6.
    
    int z = 5;
    int y = x++;  // 先赋值给y,然后改变x。x为6,y为5. 
  • 副作用及顺序点:
    • 副作用指在计算表达式时对某些东西进行了修改。
    • 顺序点是程序执行的一个点,在这里,进入下一步之前确保对所有副作用进行评估。在C++中,分号就算一个顺序点。另外任何一个完整表达式都是一个顺序点。完整表达式的例子:表达式语句中的表达式部分以及用作while循环中检测条件的表达式。
      while (guest++ < 10)
        printf("%d\n", guests);  // 与C++中一致,如果guest为9,则因为while中表达式为一个顺序点,因此完成这个表达式之后guest加1,打印出来为10。要注意此种编程带来的影响!!!
  • 语句块
    • 使用{}包含多个语句
    • 在语句块内声明的变量,生命周期为语句块内的范围,在语句块内创建,结束执行语句块后释放对应内存。
  • 逗号操作符
    • 允许一个语句包含多个表达式。
    • 逗号操作符是一个顺序点,确保先计算第一个表达式,再计算第二个表达式。
    • C++规定,逗号表达式的值为第二部分的值。如果包含多个表达式,就以最后一个表达式的值为逗号表达式的值。

关系表达式

  • 字符串的比较
    • c-风格字符串不能使用形如word == "mate"(word为数组名)来进行字符串比较。因为实际上只是对地址的比较。而是使用函数strcmp()
    • string类字符串的比较,可直接使用关系操作符。不过需满足至少一个操作数为string类对象方可使用。

while 循环

  • 循环语句:
    while (test-condition)   // 测试条件为真,则执行循环体
      body

do while 循环

  • 循环语句:
    do
      body
    while (test-condition);

循环和文本输入(后续添加)

  • cin对象支持3种不同模式的单字符输入:
    1. 使用原始的cin进行输入
    char ch;
    cin >> ch
    while (ch != '#')  // 设置‘#’为哨兵字符,以标志停止输入
    {
      ...  // 注意cin对象是从缓冲区读取字符,如果字符停止输入,但缓冲区仍有字符,则下次使用cin时会直接从缓冲区读取,而不是请求键盘输入
    }  // cin对象会忽略空白符及换行符!!!
    1. 使用cin.get(char)接收任意字符
    2. 注意cin.get(),cin.get(char)cin.get(name, arrsize),它们属于C++函数重载的oop特性,允许同名函数,但是参数列表不同。

二维数组

  • 二维数组是在一维数组的基础上构建,并不是C++内部提供的类型。
  • 二维数组声明及初始化:
    int maxtemps[4][5] = 
    {
      {94, 98, 97, 103, 101},
      {94, 98, 97, 103, 101},
      {94, 98, 97, 103, 101},
      {94, 98, 97, 103, 101}
    };

类型别名

  • 使用预处理器:
    #define BYTE char 这样,预处理器将在编译程序时用所有char替换所有的BYTE。
  • 使用typedef创建别名:
    typedef typeName aliasName;
    typedef char byte; //  使用byte作为char的别名
    typedef不会创建新类型,而是为已有类型建立一个新的名称。typedef 比 #define 能处理更复杂的类型别名。

chapter 6

if语句及if-else语句

if (test-condition)  // 测试条件为真,则执行语句
  statement

if (test-condition)
  statement1  // 如果多个语句, 则用花括号{}括起
else
  statement2

if (test-condition)
  statement1
else if (test-condition)
  statement2
else
  statement3

逻辑表达式

  • 逻辑或操作符(||)
  • 逻辑与操作符(&&)
    • 取值范围测试:不要使用形如17 < age < 35这种数学形式的表达式,其实际结果为(17 < age) < 35的逻辑值,应用完整逻辑表达式,如(17 < age) && (age < 35)
  • 逻辑非操作符(!)

switch语句

  • 语句格式:
    switch (integer-expression)
    {
      case label1: statement
      ...
      default: statement
    }
  • 注意:
    • integer-expression 必须为结果是整数值的表达式;每个标签必须是整数常量的表达式(如int, char类型及枚举量)。
    • 程序跳转到switch特定代码时,将依次执行之后的语句,因此如果需要中止跳转则使用break,但是注意此处的break只能退出switch语句块,如果switch语句块包含在一个大循环中,无法使用break退出循环 !
    • 无法用于浮点测试及两个变量的比较,因为case标签值要求为常量。
    • if-else也可以用于多选项,但是当选项多于3个时,使用switch语句效率更高。

break和continue语句

  • brak语句:跳过循环体剩余部分,并结束循环进入下一条语句。
  • continue语句:跳过循环体剩余部分,并继续循环,开始下一轮循环。

读取数字的循环

  • 假设要将一系列数字读取入数组中
    int n;
    cin >> n;
    如果输入的是一个单词而不是一个数字,发生类型不匹配时,将有四种情况:
    • 已读入的内容 n 的值将保持不变。
    • 不匹配的输入将被一直保留在输入队列中。
    • cin 对象中的一个错误标记被设置。
    • cin方法的调用将返回false(如果被转换为bool类型)。
  • cin方法调用返回false意味着可以用非数字输入来结束读取数字的循环
  • 非数字输入的设置错误标记意味着必须重置改标记,程序才能继续读取输入。调用cinclear()方法来重置错误输入标记,同时也重置文件尾。
  • cin >> 的操作实际上也是cin方法函数的调用
  • cin输入了错误内容,为了重新正常输入,应采取3个步骤:
    1. 重置cin以接受新的输入。
    2. 删除错误输入。
    3. 提示用户再输入。
    while (!(cin >> golf[i]))
    {
      cin.clear();  // 重置输入
      while (cin.get() != '\n')  // 将队列中的输入清除
       continue;
     cout << "提示输入";
    }

简单文件输入/输出(待补充)

chapter 7

C++函数基本知识

  • 使用C++函数前基本工作:
    • 提供函数定义
    • 提供函数原型
    • 调用函数
  • 函数定义
    • typeName func(paramlist)
      {
        statements
        return value;
      }
    • void 函数无返回值函数
    • 对于有返回值函数,必须使用返回语句将值返回给调用函数。值本身可以是常量,变量或表达式。
    • 返回值的类型必须为声明的返回类型或可以被转换为typeName。不过C++对返回值类型有一定的限制,不能是数组,但可以是其他任何类型,包括结构对象以及包含数组元素的结构对象。
    • 函数的返回结果会存储在一个临时返回内存单元内。
  • 原型的功能
    • 编译器正确处理函数返回值
    • 编译器检查使用的参数数目是否正确
    • 编译器检查使用的参数类型是否正确,如果不正确,则转换成正确的类型。

函数参数与按值传递

  • 函数的参数为形参,实际传递到函数的参数为实参
  • 形参实际上是函数调用后自动创建的变量,伴随着函数的生命周期产生到消失
  • 按值传递是指实参将其值复制拷贝给函数形参的过程

函数与数组

  • 函数头:int sum_arr(int arr [], int n) ,其中int arr []声明了传递的是数组参数,实际上 arr 为指针,声明等价为 int * arr
  • 函数传递常规变量时,函数将使用该变量拷贝;但传递数组时,函数将使用原来的数组,不过函数形参接收数组指针地址,因此在函数内部应用sizeof时得到的是指针变量字节大小而不是原数组大小。
  • 指针与 const
    • 使用 const int * 声明指针变量时,表示无法通过指针修改指向的变量,而不是指指针指向的变量为常量!
    • 不能将常量地址赋值给常规指针,如 char * p = "abc" 是非法的,但是 const char * p = "abc" 合法。
    • 总的说,如果数据类型本身并不是指针,则可以将const数据或非const数据的地址赋给const的指针,但只能将非const数据地址赋值给非const指针。
    • 尽可能使用const,函数中应将指针形参声明为const指针!!
    • 但是!如果函数形参为指向指针的指针或数组元素为指针,则不能使用 const

函数与二维数组

  • 函数声明:
    int sum(int (* arr2) [4], int size);  // 两种声明一样
    int sum(int arr2[][4], int size);
  • 二维数组的数组名实际上为指向向指针的指针。其作为函数形参不使用const
  • int arr2[3][4]为例解析二维i数组数组名:
    • arr2 表示指向第一行的4元素的数组指针
    • arr2 + r 表示第 r+1 行4元素数组的指针
    • *(arr2 + r) 表示第 r + 1 行数组名,也就是第 r + 1 行第一个元素的地址
    • *(*(arr2 + r) + c) 表示第 r + 1 行数组第 c + 1 个元素。即 arr2[r][c]

函数和C—风格字符串

函数和结构

未完待续 . . .

About

C++ Primer Plus 第五版编程练习题集合

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages