《C++ Primier Plus》笔记:基础与函数部分(ch2开始学习C++;ch7函数;ch8函数探幽)

Chapter2 开始学习C++

2.1.4 头文件名

  • 头文件后缀不是.h代表不是传统C库,可以使用命名空间。类、函数、变量才是C++编译器的标准组件,它们都被放置在名称空间std中

2.1.6 使用cout进行C++输出

  • cout是一个预定义的对象
  • C本身也有一些运算符重载的情况。例如&符号即代表地址运算符,也代表按位与运算符;*既表示乘法,也表示解除引用:编译器根据上下文来确定含义

2.2.1 声明语句和变量

  • 程序中的声明语句叫做defining declaration,简称为定义(definition),它代表编译器将分配内存空间
  • 声明(declaration)不一定是定义,还有可能是reference declaration

2.2.2 赋值语句

  • C和C++有特殊的性质:可以连续使用赋值符号

2.4.1 使用有返回值的函数

  • 被调用的函数叫做被调用函数(called function),包含函数调用的函数叫做调用函数(calling function)
  • 函数原型之于函数就像变量声明之于变量
  • 在程序使用sqrt()时,必须提供原型;可以用两种方法实现:
    • 在源代码中输入函数原型
    • 包含头文件cmath, 其中定义了原型
  • 使用库函数:C++库函数储存在库文件中,编译器编译时必须在库文件中搜索你使用的函数。
  • 只包含cmath头文件可以提供原型,但不一定会导致编译器搜索正确的库文件

2.4.3 用户定义的函数

  • 标准C库提供了140个预定义的函数
  • 和C一样,C++不允许将函数定义在函数中
  • main函数的返回值返回给系统
  • 关键字:main不是关键字,可以把main作为变量名(不推荐)
  • 在程序中将不能将同一个名字作为对象名与变量名(比如在使用cout输出的程序中将cout用作变量名)

2.4.5 在多函数程序中使用using编译指令

  • 让程序能访问名称空间std的四种方法:
    • 将using namespace std; 放在函数定义之前,让文件中所有的函数都能使用std中所有元素
    • 将using namespace std; 放在特定的函数定义内,让该函数能使用std中所有元素
    • 在特定的函数中使用类似using std::out, 让该函数能使用特定的元素
    • 完全不用编译指令using, 而在使用元素时加上std::前缀

Chapter7 函数

7.1 复习函数的基本知识

  • 库函数是已经定义和编译好的函数,同时可以使用标准库头文件提供其原型

7.1.1 定义函数

  • C++规定函数返回值不能是数组,但可以是其他任何类型

7.1.2 函数原型和函数调用

  • 原型描述了函数到编译器的接口,将函数返回类型和参数列表告知编译器,因此编译器不用停止编译main函数而在文件内部或其他文件中寻找。
  • 函数原型中可以包含变量名,也可以不包含,甚至不需要和函数定义中的变量名相同
  • ANSI C中的原型是可选的,但在C++中原型是必不可少的
  • C++中say_hi()和say_hi(void)等效,表示函数没有参数;C中say_hi()表示将在后面定义参数列表
  • 原型的功能:
    • 确保编译器正确处理参数返回值
    • 确保编译器检查使用的参数数目是否正确
    • 确保编译器检查使用的参数类型是否正确;若不正确则转换为正确的类型
  • 通常原型自动将被传递的参数强制转换为期望的类型;只会进行有意义的转换,如不会将指针转换为整型
  • 在编译阶段进行的原型化成为静态类型检查

7.2 函数参数和按值传递

  • 用于接受传递值的变量成为形参(parameter),传递给函数的值称为实参(argument)

7.2.2 另一个接受两个参数的函数

  • 函数中使用两种局部变量:形参和其他局部变量

7.3.1 函数如何使用指针来处理数组

  • C++将数组名解释为其第一个元素的地址
    cookies = &cookies[0]
  • 该规则存在一些例外
    • 数组声明使用数组名来标记存储位置
    • 对数组名使用sizeof()将返回整个数组的长度(以字节为单位)
    • 将地址运算符&用于数组名时,将返回整个数组的地址(包含数组总大小个字节)
  • C++中当且仅当用于函数头或函数原型中,int* arr 和int[] arr是等价的,都意味着arr是一个int*指针(而不是数组):这也是必须向数组传递数组大小的原因,int* 指针无法指出数组长度
  • 将指针加一,是加上了一个sizeof(指针类型)
    • arr[i] == *(arr + i)
    • &arr[i] == arr + I
  • 使用原始数据作为函数实参增加了破坏数据的风险,ANSI C和C++中的const限定符提供了解决这种问题的方法:不能通过const修饰的指针修改其指向的值

7.3.3 更多数组函数示例

  • 自上而下和自顶向下的模式:
    1. 考虑通过数据类型和设计适当的函数来处理数据,然后将函数合成依序,是为自下而上的程序设计(Bottom-Up)。因为设计过程从组件到整体进行,首先强调数据的表示和操纵,这种方法适用于OOP。
    2. 传统的过程性编程倾向于自顶而下的模式(Top-Down),首先指定模块化设计方案,再研究细节

7.3.4 使用数组区间的函数

  • STL方法使用“超尾”概念来指定区间,对于数组而言,标识数组结尾的参数将是指向最后一个元素后面的指针。
    • 首3个元素:cookies, cookies+3
    • 总数8个,后4个元素:cookies + 4, cookies + 8

7.3.5 指针和const

  • 有两种方法将const用于指针

    • 让指针指向一个常量对象,可以防止使用该指针来修改所指向的值
    int age = 39;
    const int* pt = &age;
    

    这里pt指向一个const int,意味着不能使用pt来修改这个值;另外这里将变量的地址赋给常量指针,是可以的。但不可以把常量赋给非常量指针。

    • 将指针本身声明为常量,可以防止改变指针指向的位置
    int sloth = 3;
    int* const finger = &sloth;
    
  • 当进入两级间接关系时,将const和非const混用将不安全。因此仅当只有一层间接关系时,才可以将非const地址或指针赋给const指针

    const int **pp2;
    int *p1;
    const int n = 13;
    pp2 = &p1;      //const int** = &int*
    *pp2 = &n;      //*const int** = & const int
    *p1 = 10;       //* int* = 10;
    

    这段代码中,p1不能直接指向n,就通过pp2指向p1,从而改变了const的p1值

  • 尽可能使用const:

    • 这样可以避免由于无意间修改数据而导致的编程错误
    • 使用const使得函数可以处理const和非const实参,否则只能接受非const数据。

7.4 函数和二维数组

  • 向函数传递二维数组的原型:
    int sum(int (*ar2)[4], int size)
    ar2是指针而不是数组,且指针类型指定了列数

  • 与指针数组的区别:

    指向int[4]的指针: int(*ar2)[4]
    4个指向int的指针:int* ar2[4]
    指向int[4]的指针:int ar2[][4]
    
  • 二维数组的索引

    ar2[r][c] == *(*(ar2 + r) + c)  //注意不是*((ar2 + r) + c)
    

7.5 函数和C-风格字符串

  • 将字符串作为参数时意味着传递的是地址,但可以使用const来禁止对字符串参数进行修改
  • 实际传递的是字符串第一个字符的地址,因此形参声明应该是char*
  • 不必传递字符串长度,因为可以检验莫问的控制字符’\0’

7.6 函数和结构

  • 在函数中使用结构有三种方法
    • 按值传递结构:将实参复制一份,赋值给形参
    • 通过指针传递
    • 通过引用传递
  • 按值传递的缺点是,如果结构非常大,则复制结构将增加内存要求,降低运行速度

7.7 函数和string对象

  • 与数组相比,string对象和结构更类似:
    1. 可以将结构\对象赋给另一个结构\对象
    2. 可以将结构\对象作为完整的实体传递给函数
    3. 如果需要多个字符串,可以声明一个string对象数组,而不是二维char数组

7.8 函数和array对象

  • 要使用array类,需要包含头文件array(位于namespace std中)

7.9.1 包含一个递归调用的递归

  • 与C不同的是,C++不允许main调用自己
  • 递归模板1:
    void recurs(argumentlist){
        statements1
        if(test){
            recurs(arguments)
        }
        statements2
    }
    
    只要if语句为true,每个recurs()调用都将调用statements1,然后再调用recurs()。(如果if语句为false则会执行statements2)
    当前调用结束后返回,以相反的顺序调用statements2

7.9.2 包含多个递归调用的递归

// ruler.cpp -- using recursion to subdivide a ruler
#include <iostream>
const int Len = 66;
const int Divs = 6;

void subdivide(char ar[], int low, int high, int level);
int main(int argc, char const *argv[])
{
    char ruler[Len];
    int i;
    for (i = 1; i < Len - 2; i++)
        ruler[i] = ' ';
    ruler[Len - 1] = '\0';
    int max = Len -2;
    int min = 0;
    ruler[min] = ruler[max] = '|';
    std::cout << ruler << std::endl;
    for (i = 1; i <= Divs; i++)
    {
        subdivide(ruler,min,max,i);
        std::cout << ruler << std::endl;
        for (int j = 1; j < Len - 2; j++)
            ruler[j] = ' ';      // reset to blank ruler
    }
    return 0;
}

void subdivide(char ar[], int low, int high, int level)
{
    if (level == 0)
        return;
    int mid = (high + low) / 2;
    ar[mid] = '|';
    subdivide(ar, low, mid, level - 1);
    subdivide(ar, mid, high, level - 1);
}

输出:

|                                                               |
|                               |                               |
|               |               |               |               |
|       |       |       |       |       |       |       |       |
|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

  • 拥有N个子递归语句的函数,若调用深度为level,则调用次数为 N l e v e l N^{level} Nlevel

7.10 函数指针

  • 函数也有地址,函数的地址是存储其机器语言代码的内存的开始地址
  • 不带()的函数名返回函数的内存地址
  • 函数指针的声明应该指定函数的返回类型和函数的特征标,例如:double (*pt) (int, float)
  • 函数指针可以作为函数参数,例如:void estimate(int lines, double (*pt)(int))
  • 可以允许像使用函数名一样使用函数指针

7.10.3 深入探讨函数指针

恐怖的章节,没看懂。不过大致讨论了C++11采用auto的必要性,快说,感谢auto

Chapter8 函数探幽

8.1 C++内联函数

  • 程序的调用流程:

    • 存储该指令的内存地址
    • 将函数参数复制到堆栈中
    • 跳到标记函数起点的内存单元,执行函数代码
  • 内联函数的运行速度比常规函数稍快,但代价是占用更多内存,再N个地方调用将产生N个副本

  • 如果代码执行时间很短,且常被调用就可以节省更多时间

  • 要使用内联特性需要:

    • 在函数声明前加上inline
    • 在函数定义前加上inline
  • 如果编译器认为函数过大或函数调用了自己(递归不能内联)则会拒绝将其作为内联函数

  • 如果C语言用宏实现了某功能,应考虑将其转化为C++的内联函数

8.2.1 创建引用变量

  • 引用变量的主要用途是用作函数的形参,函数将使用原始数据
  • 引用变量和其引用的变量指向相同的值和内存单元
  • 必须在声明引用时将其初始化,而不能像指针那样,先声明后赋值
  • "int & rodents = rats"实际上是"int * const pr = &rats"的伪装

8.2.2 将引用用作函数参数

  • 将引用作为函数参数称为按引用传递,该参数被初始化为实参

8.2.3 引用的属性和特别之处

  • 当向函数传递数据很大时,推荐使用引用类型
  • 传递引用的限制更严格,比如x+3不能赋值给引用,因为其不是变量
  • 如果实参和引用参数不匹配,C++将生成临时变量。当前仅当参数为const引用时,C++采允许这样做:
    • 当实参的类型正确,但不是左值
    • 当实参的类型不正确,但可以转换为正确的类型
  • 传统返回机制计算return后的表达式将结果返回给调用函数。这个值被复制到一个临时位置,而调用程序使用这个值
  • 返回引用的函数实际上是被引用的变量的别名
  • 返回引用应该注意,避免返回函数终止时不再存在的内存单元引用。因此可以:
    1. 返回传入函数的参数引用
    2. 用new来分配变量,并返回
  • 如果需要返回不可赋值的引用,可以加上const

左值

左值是可以被引用的数据对象,例如:

  • 变量
  • 数组元素
  • 结构成员
  • 引用和接触引用的指针

非左值包括:

  • 字面常量(有引号括起的字符串除外,它们由地址表示)
  • 包含多项的表达式

常规变量和非const变量都可视为左值,因为可通过地址访问他们,但常规变量属于可修改的左值,const变量属于不可修改的左值

8.2.5 将引用用于类对象

  • 如果形参为const string&, 在调用函数时实参可以是string对象或C风格字符串,如:
    • 用引号括起的字符串字面量
    • 以空字符结尾的char数组
    • 指向char的指针变量

8.2.6 对象、继承和引用

  • 继承的一个特性是基类引用可以指向派生类对象,而无需进行强制类型转换。可以将基类指针作为函数参数

8.2.7 何时使用引用参数

  • 使用引用参数的原因有:
    • 能修改调用函数中的数据对象
    • 提高程序的运行速度
  • 对于使用传递的值而不修改的函数
    • 如果数据对象很小,如内置数据类型或小型结构,则按值传递
    • 如果数据对象是数组,则使用指针,并改为const
    • 如果数据对象是较大的结构,则使用const指针或const引用
    • 如果数据对象是类对象,则使用const引用
  • 对于修改调用函数中数据的函数
    • 如果数据是内置数据类型,则使用指针
    • 如果数据是数组,则使用指针
    • 如果数据是结构,则使用引用或指针
    • 如果是类对象,则使用引用

8.3 默认参数

  • 通过使用默认参数,可以减少要定义的析构函数,方法以及方法重载的数量
  • 只有原型指定了默认值

8.4 函数重载

  • 函数重载的关键是参数列表(函数特征标)
  • 如果两个函数的参数数目和类型相同,且参数的排列顺序相同,则其特征标是相同的,而变量名是无关紧要的
  • 如果没有匹配的原型,C++将使用标准类型转换强制进行匹配
  • 编译器检查特征标时,将引用和引用的类型视为同样的
  • 返回类型是不能重载的,特征标是针对函数列表的。返回类型不同,那么函数名相同时特征标也必须不同

重载引用参数

void sink(double& r1);
void sank(const double& r2);
voud sunk(double&& r3)
  • 左值引用参数r1和可修改的佐治参数(如double变量)匹配
  • const左值引用r2与可修改的左值、const左值参数和右值参数(如两个double值的和)匹配
  • 右值引用参数r3和右值匹配

如果重载使用这三种参数的函数,将调用最匹配的版本:

void staff(double& rs);  //matches modifiable lvalue
void staff(const double& rcs); //matches rvalue, const lvalue
void stove(double& r1);  //matches modifiable lvalue
void stove(const double& rcs);  //matches const lvalue
void stove(double&& r3);        //matches rvalue

以此决定函数的行为:

double x = 55.5;
const double y = 32.0;
stove(x);   //calls stove(double& )
stove(y);   //calls stove(const double&)
stove(x+y)  //calls stove(double&& )

8.4.2 何时使用函数重载

  • 仅当函数执行相同任务,但使用不同形式的数据时才应使用函数重载
  • 名称修饰:根据原型中指定的形参类型对每个函数名加密

8.5 函数模板

  • 关键词template和typename是必须的,typename和class是互换的

8.5.1 重载的模板

  • 可以像重载常规函数定义那样重载模板定义

8.5.3-8.5.4 显式具体化、实例化和具体化

  • 在代码中包含函数模块本身并不会生成函数定义,它只是一个用于生成函数定义的方案。

  • 对于给定的函数名,可以有非模板函数,模板函数和显式具体化模板函数以及它们的重载版本

  • 包括三种

    • 隐式实例化(implicit instantiation)

      编译器使用模板为特定类型生成函数定义时,得到的是模板实例(instantiation)。模板并非是函数定义,但使用int的模板实例是函数定义

      Swap(a,b);
      
    • 显式具体化(explicit specialization):

      显式具体化的原型和定义应以template<>打头,并通过名称来指出类型

      //模板函数原型
      template <class T>
      void Swap(T&, T&)
      
      //具体化的原型,类job
      template<> void Swap<job>(job&, job&)
      
    • 显式实例化(explicit instantiation)

      直接命令编译器创建具体的实例:

      template void Swap<int>(int, int)
      

8.5.5 编译器选择使用哪个函数版本

  • C++的重载解析:

    1. 创建候选函数列表,包含与被调用函数的名称相同的函数和模板函数
    2. 使用候选函数列表创建可行函数列表:都是参数数目正确的函数,并有一个隐式转换序列
    3. 确定是否有最佳的可行函数,如没有则调用出错
  • 从最优到最差排序:

    1. 如果只存在一个这样的函数,则选择它
    2. 完全匹配,但常规函数优于模板函数;如果都是模板函数,则较具体的优先
    3. 提升转换(如char和shorts自动转换为int,float自动转换为double)
    4. 标准转换(如int转换为char,long转换为double)
    5. 用户定义的转换,如类声明中定义的转换
  • 用于找出最具体的模板的规则称为函数模板的部分排序规则(partial ordering rules)

8.5.6 模板函数的发展

  • decltype关键字(C++11)
    decltype(x) y; //使y有和x一样的类型
    decltype(x+y) z;
    
  • 后置返回类型
template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x+y)
{
    ...
    return x+y
}
//decltype在参数声明后面,因此x和y位于作用域内,可以使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值