C++PrimerPlus6-08-函数探幽

第8章 函数探幽

  • 内联函数
  • 引用变量
  • 如何按引用传递函数参数
  • 默认参数
  • 函数重载
  • 函数模板
  • 函数模板具体化

8.1 C++内联函数

提高程序运行速度。常规函数和内联函数之间的主要区别不在于编写方式,而在于C++编译器如何将他们组合到程序中。

  • 常规函数:来回跳跃
  • 内联函数:无需跳跃。编译器使用相应的函数代码替换函数调用,运行速度比常规函数快,代价是更多内存。

使用内联:

  • 在函数声明前加上inline
  • 在函数定义前加上inline
  • 通常省略原型,直接整个定义。
  • 宏#define是文本替换,不能按值传递,会出问题。
// an inline function definition
inline double square(double x) {return x * x;}

8.2 引用变量

引用是已定义的变量的别名主要用途是用作函数的形参,函数将使用原始数据,而不是其副本。

8.2.1 创建引用变量

&这里作为类型标识符的一部分,而不是地址运算符。他们指向相同的值和内存单元。

int rats;
int& rodents = rats; // makes rodents an alias for rats

引用和指针的区别:

  • 引用必须在声明时将其初始化,而指针可以先声明再赋值。
  • 引用更接近const指针。
// 这时角色相同
int& rodents = rats;
int* const pr = &rats;

8.2.2 将引用用作函数参数

用作函数参数,使得函数中的变量名成为调用程序中的变量的别名。

void swapr(int& a, int& b); // a b are aliases for ints,引用传值,可以修改原值
void swapp(int* p, int* q); // p q are addresses of ints,传指针,可以修改原值
void swapv(int a, int b); // a b are new variables,按值传递,不能修改原值

8.2.3 引用的属性和特别之处

如果想用引用,又不对信息进行修改,应使用常量引用(可按值传递):

double refcube(const double &ra);

实参和引用参数不匹配时,C++生成临时变量(如果现在的C++标准会禁止创建临时变量)。当参数是const引用,C++才允许:

double refcube(const double &ra)
{
  return ra * ra * ra;
}

douoble side = 3.0;
double* pd = &side;
double& rd = side;
long edge = 5L;
double lens[4] = {2.0, 5.0, 10.0, 12.0};

double c1 = refcube(side);          // ra is side
double c2 = refcube(lens[2]);       // ra is lens[2]
double c3 = refcube(rd);            // ra is rd is side
double c4 = refcube(*pd);           // ra is *pd is side
double c5 = refcube(edge);          // ra is temporary variable,临时变量。变量类型不正确
double c6 = refcube(7.0);           // ra is temporary variable,临时变量
double c7 = refcube(side + 10.0);   // ra is temporary variable,临时变量

尽可能使用const引用:

  • 能避免无意中修改数据的编程错误
  • 使函数能处理const和非const实参,否则将只能接受非const数据
  • 使函数能够正确生成并使用临时变量

&&右值引用(第18章)(rvalue reference),区别于&声明的左值引用:

double&& rref = std::sqrt(36.00); // not allowed for double&
double j = 15.0;
double&& jref = 2.0 * j + 18.5;   // not allowed for double&
std::cout << rref << '\n';        // display 6.0
std::cout << jref << '\n';        // display 48.5

8.2.4 将引用用于结构

可以返回引用(区别于传统返回),返回引用的函数实际上是被引用的变量的别名:

struct free_throws
{
  std::string name;
  int made;
  int attempts;
  float percent;
};

free_throw& accumulate(free_throw& target, const free_throws& source);

accumulate(team, five) = four; // 返回引用,并能给他赋值

返回引用要避免返回函数终止时不在存在的内存单元引用(例如函数内部的局部变量):

  • 可以返回一个作为参数传递给函数的引用
  • 可以new,配合delete

8.2.5 将引用用于类对象

当形参是const string&时,实参可以是string也可以是const char*:

  1. string定义了char*到string的转换。
  2. const引用的形参,当参数类型不匹配时,会进行转换,创建正确类型的临时变量。

8.2.6 对象、继承和引用

ofstream(文件输入输出)是ostream(控制台输入输出)的派生类。
setf()设置格式化状态(第17章)。setf(ios_base::fixed)将对象置于使用定点表示法的模式,setf(ios_base::showpoint)显示小数点。

8.2.7 何时使用引用参数

使用引用参数的主要原因:

  • 能够修改调用函数中的数据对象。
  • 通过传递引用而不是整个数据对象,提高程序的运行速度。

分别使用引用、指针、按值传递的指导原则:

  • 传递的值不修改时:
    • 数据很小,按值传递。内置数据、小型结构
    • 数据是数组,用指针。这是唯一的选择,并用const指针
    • 较大的结构,用const指针或const引用。提高效率
    • 类对象,使用const引用。类设计的语义常常要求用引用
  • 要修改调用函数中的数据:
    • 数据对象是内置数据类型,用指针。
    • 数组,只能用指针。
    • 结构,用引用或指针。
    • 类对象,用引用。

8.3 默认参数

默认参数是当函数调用中省略了实参时自动使用的值。
通过函数原型来设置默认值,将值赋给原型中的参数。必须从右向左添加默认值,如果要为某个参数设置默认值,则必须为它右边的左右参数提供默认值:

char* left(const char* str, int n = 1);
int harpo(int n, int m = 4, int j = 5); // VALID
int chico(int n, int m = 6, int j);     // INVALID

// 必须从左到右给实参,不能跳过
beeps = harpo(3, , 8);                  // INVALID

8.4 函数重载

函数重载(多态)能使用多个同名的函数。它们使用不同的参数列表。
区分函数的参数列表(特征标function signature),如果参数数目或参数类型不同,特征标不同。

为避免混乱,类型引用和类型本身视为同一特征标(实参和他们都能匹配)。如果参数有引用、const引用、右值引用三个版本,而引用和右值引用的实参都能和const引用匹配,程序将调用最匹配的版本。

8.4.1 重载示例

8.4.2 何时使用函数重载

仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载。
C++编辑器通过名称修饰(name decoration)或名称矫正(name mangling)来区分重载函数,会将long MyFunctionFoo(int, float)在内部表示为这种:?MyFunctionFoo@@YAXH

8.5 函数模板

函数模板是通用的函数描述,他们使用泛型来定义函数(通用编程)。
模板不创建函数,只是告诉编译器如何定义函数,需要用到时再根据具体类型创建函数。

template <typename T> // or class T
void Swap(T &a, T &b);

// function template definition
template <typename T> // or class T
void Swap(T &a, T &b)
{
  T temp; // temp a variable of type T
  temp = a;
  a = b;
  b = temp;
}

8.5.1 重载的模板

同重载常规函数

8.5.2 模板的局限性

编写的模板函数可能无法处理某些类型,例如a*b如果T为数组、指针、结构则不成立。要通用化,对策有:1)运算符重载;2)为特定类型提供具体化的模板定义。

8.5.3 显式具体化

第三代具体化(ISO/ANSI C++标准):

  • 对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本。
  • 显式具体化的原型和定义应以template<>开头,并通过名称来指出类型。
  • 具体化优先于常规模板,而非模板函数优先于具体化和常规模板。
// non template function prototype
void Swap(job&, job&);

// template prototype
template <typename T>
void Swap(T&, T&);

// explicit specialization for the job type
template <> void Swap<job>(job&, job&);

8.5.4 实例化和具体化

显式实例化(explicit instantiation)和显式具体化(explicit specialiazation)有区别:

  • 显示实例化template是使用模板生成一个特定类型的实例
  • 显式具体化template<>是不用模板来生成,而是使用专门的来定义。他的原型有自己的定义
  • 隐式实例化、显式实例化、显式具体化统称为具体化(specialization)
template <class T>
void Swap(T&, T&); // template prototype,模板原型

template <> void Swap<job>(job&, job&); // 显式具体化
int main(void)
{
  template void Swap<char>(char&, char&); // 显式实例化
  
  short a, b; 
  Swap(a, b); // 使用模板原型的隐式实例化

  job n, m;
  Swap(n, m); // 使用显式具体化

  char g, h;
  Swap(g, h); // 使用显式实例化
}

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

重载解析(overloading resolution):使用 函数重载、函数模板、函数模板重载 的策略:

  • Step1:创建候选函数列表
  • Step2:使用候选函数列表创建可行函数列表
  • Step3:确定是否有最佳的可行函数

确定最佳可行函数的优先级,上面最优先:

  1. 完全匹配,且常规函数>模板
  2. 提升转换(例如char和short提升为int,float提升为double)
  3. 标准转换(例如int转换为char,long转换为double)
  4. 用户定义的转换(例如类声明中定义的转换)

如果多个匹配,编译器无法完成重载解析,报错二义性(ambiguous)。

8.5.6 模板函数的发展

当要用的变量类型不明确时,使用decltype关键字(C++11):

int x;
decltype(x) y; // make y the same type as x

template<clatt T1, class T2>
void ft(T1 x, T2 y)
{
  ...
  decltype(x + y) xpy = x + y;
  ...
}

如果返回值的类型不明确,可使用后置返回类型(trailing return type):

template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x + y)
{
  ...
  return x + y;
  ...
}

8.6 总结

  • 内联函数inline
  • 引用变量
  • 默认参数
  • 函数重载(函数多态),函数的特征标:参数列表
  • 函数模板

8.7 复习题

8.8 编程练习

1 略
2

3 getline(cin, str)。
更好的写法是不用temp,直接修改原字符串:

for (int i = 0; i < s.size(); i++)
{
  s[i] = toupper(s[i]);
}

4

#include <iostream>
#include <cstring> // for strlen(), strcpy()
using namespace std;

struct stringy
{
    char* str;        // points to a string
    int ct;            // length of string (not counting '\0')
};

// prototypes for set(), show(), and show() go here
void set(stringy& strg, const char* ar);
void show(const stringy& strg, int num = 1);
void show(const char* ar, int num = 1);

int main()
{
    stringy beany;
    char testing[] = "Reality isn't what it used to be.";

    set(beany, testing);
    show(beany);        // prints member string once
    show(beany, 2);        // prints member string twice
    testing[0] = 'D';
    testing[1] = 'u';
    show(testing);        // prints testing string once
    show(testing, 3);    // prints testing string thrice
    show("Done!");
    return 0;
}

void set(stringy& strg, const char* ar)
{
    char* pt = new char[strlen(ar)];
    strg.str = pt;
    strcpy(pt, ar);
    strg.ct = strlen(strg.str);
}

void show(const stringy& strg, int num)
{
    cout << "Need print " << num << " times:\n";
    for (int i = 0; i < num; i++)
    {
        cout << strg.str << endl;
    }
}

void show(const char* ar, int num)
{
    cout << "Need print " << num << " times:\n";
    for (int i = 0; i < num; i++)
    {
        cout << ar << endl;
    }
}

5

6 这返回的是字符串地址吗?

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

template <typename T>
T maxn(T ar[], int size);

template<> char* maxn<char*>(char* ar[], int size);

int main()
{
    int ari[6] = { 2, 4, 6, 7, 5, 1 };
    double ard[4] = { 23.1, 54.2, 35.5, 15.7 };
    const char* arc[5] = { "des", "dqrer", "dqrewta4", "dqt4qte", "dqretaqete" };

    cout << maxn(ari, 6) << endl;
    cout << maxn(ard, 4) << endl;
    cout << maxn(arc, 5) << endl;

    return 0;
}

template <typename T>
T maxn(T ar[], int size)
{
    T max = ar[0];
    for (int i = 1; i < size; i++)
    {
        if (max < ar[i]) max = ar[i];
    }

    return max;
}

template<>
char* maxn<char*>(char* ar[], int size)
{
    int max = strlen(ar[0]);
    int index = 0;
    for (int i = 1; i < size; i++)
    {
        if (max < strlen(ar[1]))
        {
            max = strlen(ar[1]);
            index = i;
        }
    }

    return ar[index];
}

7 略


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 cdd@ahucd.cn

×

喜欢就点赞,疼爱就打赏

B站 cdd的庇护之地 github itch