C++ Primer Plus第六版-第八章-学习笔记

8.1 内联函数

内联函数:只有一行代码的小型非递归函数
内联函数的运行速度比常规函数稍快,但代价是占用更多的内存。
内联函数的使用:

  • 在函数声明前加上关键字inline 或者
  • 在函数定义前加上关键字inline
    内联函数按值传递参数

8.2 引用变量

引用是已定义的变量的别名
引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据。这样除指针之外,引用也为函数处理大型数据提供了一种非常方便的途径,同时对于设计类来说,引用也是必不可少的。

引用
int rats;
int& rodents = rats; //必须在声明引用变量时进行初始化

引用经常被用作函数参数,是得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。(按值传递导致被调用函数使用调用程序的值的拷贝)函数调用使用实参初始化形参
传递引用传递指针的区别

  • 声明函数参数的方式不同
 指针与引用
 void swapr(int& a, int& b)
 void swapp(int* p, int* q)
  • 指针版本需要在函数使用p, q的整个过程中使用解除引用运算符*

临时变量、引用参数和const

如果引用参数是const,则编译器将在下面两种情况下生成临时变量

  • 实参的类型正确,但不是左值
  • 实参的类型不正确,但可以转换为正确的类型
    左值:可被引用的数据对象(变量、数组元素、结构成员、引用和解除引用的指针)
    非左值: 字面常量(用引号括起来的字符串除外,它们由其地址表示)和包含多项的表达式
如果接受引用参数的函数的意图是修改作为参数传递的变量,则C++将禁止创建临时变量
如果函数的目的只是使用传递的值而不修改它们,C++将在必要时生成临时变量

将引用用于结构

程序清单8.6中,需要通过函数修改某一结构成员的值,这个时候按值传递显然不可行,所以要用按地址传递或者按引用传递

清单8.6
 void set_pc(free_throws& ft)
 {    
	if (ft.attempts != 0)
		ft.percent = 100.0f * float(ft.made)/float(ft.attempts);
	else
		ft.percent = 0;
}
 返回类型
 free_throws& accumulate(free_throws& target, const free_throws& source)

如果返回类型被声明为free_throws而不是free_throws&,上述返回语句将会返回target的拷贝

 free_throws dup;
 dup = accumulate(team, five);

accumulate返回值为引用,将直接把team值赋给dup,否则如果返回的是结构,则会将结构复制到一个临时的位置,再将这个拷贝复制给dup

返回引用时需要注意的问题

应避免返回函数终止时不再存在的内存单元引用(同时也应避免返回指向临时变量的指针)
避免这种问题的方法:

  • 返回一个作为参数传递给函数的引用
  • 用new来分配新的存储空间
 new
 const free_throws& clone(free_throw& ft)
 {
	free_throws* pt;
	*pt = ft;
	return *pt;
}
该函数实际返回这个结构的引用
在不需要new分配的内存时,应使用delete来释放它们

将引用用于类对象

将类对象传递给函数时,C++的通常做法是使用引用(可以使用引用,让函数将类string、ostream、istream、ofstream、ifstream等类的对象作为参数)

示例1:
string version1(const string& s1, const string& s2)
{
		string temp;
		temp = s2 + s1 + s2;
		return temp;
}

如果把参数换成string对象,结果将不变,但是使用引用效率更高
但如果返回的是引用,则不能使用函数中定义的临时变量,如下所示,是很糟糕的

返回引用
const string& version3(const string& s1, const string& s2)
{
		string temp;
		temp = s2 + s1 + s2;
		return temp;
}

如上图示例1所示,实参(input和"*")的类型分别为string和const char
string类定义了一种char
到string的转换功能,这使得可以使用C风格字符串来初始化string对象

对象、继承和引用

能够将特性从一个类传递给另一个类的语言特性被称为继承
继承特征:基类引用可以指向派生类对象,可以定义一个接受基类引用作为参数的函数,调用函数时,可以将基类对象作为参数,也可以将派生类对象作为参数

何时使用引用参数

使用引用参数的原因:

  • 程序员能够修改调用函数中的数据对象

  • 通过传递引用而不是整个数据对象,可以提高程序的运行速度
    使用引用、指针、按值传递的指导原则
    对于使用传递的值而不做修改的函数:

  • 如果数据对象很小,如内置数据类型或小型结构,则按值传递。

  • 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。

  • 如果数据对象是较大的结构,则使用const指针或const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类参数的标准方式是按引用传递。
    对于修改调用函数中数据的函数:

  • 如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码(其中x是int),则很明显,该函数将修改x

  • 如果数据对象是数组,则只能使用指针

  • 如果数据对象是结构,则使用引用或指针

  • 如果数据对象是类对象,则使用引用

8.3 默认参数

设置默认值必须通过函数原型
对于带参数列表的函数,必须从右向左添加默认值。

参数
int harpo(int n, int m = 4, int j = 5);
int groucho(int k = 1, int m = 2, int n = 3);

实参按从左到右的顺序依次被赋给相应的形参,而不能跳过任何参数。

用new创建一个新字符串,以存储被选择的字符

创建new
char* left(const char* str, int n)
{
	if (n < 0)
		n = 0;
	char* p = new char[n+1];
	int i;
	for (int i = 0; i < n && str[i]; i++)
		p[i] = str[i];
	while (i <= n)
		p[i++] = '\0';
	return p;
}

如果输入的字符串长度小于n,显然浪费了空间,所以用下面两种方法去创建恰好够用的空间

法二
int len = strlen(str);
n = (n < len) ? n : len;
char* p = new char[n + 1];

但由于添加了一个函数调用(strlen()),使得程序更长,运行速度降低
或者可以考虑如下代码,可以省掉strlen()函数调用

法三
int m = 0;
while (m <= n && str[m] != '\0')   //可替换为while (m <= n && str[m])
	m++;
char* p = new char[m + 1];

8.4 函数重载

函数重载的关键是函数的参数列表,也称为函数特征标。特征标(参数数目和类型,参数排列顺序)
编译器在检查函数特征标时,将把类型引用和类型本身视为同一特征标。

 void dribble(char* bits);
 void dribble(const char* cbits); //重载
 void dabble(char* bits); //只能匹配非const指针
 void drivel(const char* bits); //既能匹配const指针,也能匹配非const指针

返回类型可以不同,但特征标也必须不同

double gronk(int n, float m);
long gronk(float n, float m);
重载引用参数

类设计和STL经常使用引用参数,会调用最匹配的版本

void stove(double& r1);  //matches modifiable lvalue
void stove(const double& r2); //matches const lvalue,rvalue or modifiable
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&&)

重载示例

要获得9位数字的前4位:

ct = digits - ct; //digits位数字总位数,ct位要获得的位数
while (ct--)
	num /= 10;
return num;

何时使用函数重载

仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载。

8.5 函数模板

如果需要多个将同一种算法用于不同类型的函数,请使用模板

void Show(int a[]); //其中 在函数头或函数原型中,int* a和int a[]含义相同

模板的局限性

编写的模板函数很可能无法处理某些类型

显式具体化

当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。

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

实例化和具体化

template void Swap<int>(int&, int&); //显示实例化
template <> void Swap<int>(int&, int&); //显示具体化

这些声明的意思是:
不要使用Swap()模板来生成函数定义,而应使用专门为int类型显式地定义的函数定义。
不能在同一文件(或转换单元)使用同一类型的显示实例和显式具体化。
隐式实例化、显式实例化和显式具体化统称为具体化。

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

  1. 完全匹配,但常规函数优于模板
  2. 提升转换(例如,char和shorts自动转化为int,float自动转换为double)
  3. 标准转换(例如,int转换成char,long转换为double)
  4. 用户定义的转换,如类声明中定义的转换
完全匹配和最佳匹配

进行完全匹配时,C++允许某些“无关紧要的转换”
具体见书本P290
const和非const的区别仅适用于指针和引用指向的数据
非模板优先于模板函数
较具体模板优先于普通模板
具体指的是编译器腿短使用哪种类型时执行的转换最少

double* pd[3]; //pd为指针数组
template<class T>
T lesser(T a, T b)
{
	return a < b ? a : b;
int m = 20;
int n = -30;
double x = 15.5;
double y = 25.9;
lesser<>(m,n); //<>指出编译器选择模板函数
lesser<int>(x, y); //x, y强制转换为int
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑着的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值