在读《c++primer》的过程中,遇到一些知识点,做一下简单的备忘
c++中函数接受的参数是引用类型的情况下,传递引用的限制非常的严格:
举个例子:
例如,定义函数swap(),实现两个数字值的交换:
#include <iostream>
#include <cstddef> // inlude ptrdiff_t
#include <vector>
using namespace std;
void swap(int& a, int& b); // 定义函数交换两个数的值
int main(int argc, char *argv[])
{
int a1 = 2;
int a2 = 3;
cout << "Before swap a=" << a1 << " and b=" << a2 << endl;
swap(a1, a2);
cout << "After swap a=" << a1 << " and b=" << a2 << endl;
cout << "-------------------------------" << endl;
long b1 = 5;
long b2 = 6;
cout << "Before swap a=" << b1 << " and b=" << b2 << endl;
swap(b1, b2);
cout << "After swap a=" << b1 << " and b=" << b2 << endl;
return 0;
}
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
运行结果:
书中提到,在第二种情况下,早期c++规则比较宽松,这样的操作不能实现两个数值的交换,因为实参和形参的类型不是严格的匹配,所以编译器将创建两个临时的变量,将long类型的变量转化为int,再赋值给临时变量,所以实际上swap()中进行操作的是临时变量,所以并没有实现变量的值交换。
所以总结是: 如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现,所以应该避免这种临时变量的创建
(但是再我的c++编译器下,还是成功实现了数值交换)
将引用用于结构和类:
为了提高程序的效率,在应用结构和类的时候,传递和返回参数的形式一般会使用引用,而不是按值传递,因为按值传递要栈用额外的空间和时间。再返回引用时需要注意一些问题:
例如:
// 定义一个结构
struct free_throw
{
string name;
int age;
int grade;
}
const free_throw& copy(free_throw& ft)
{
free_throw cp; // cp是一个临时变量
cp = ft;
return cp; // 返回一个临时变量的引用
}
上面的函数返回一个临时变量的引用,但是临时变量在函数执行完毕后已经销毁,所以这个引用是不存在的。同理,函数也应该避免返回临时变量的指针。
解决方法:
1. 返回作为参数传递给函数的引用:
const free_throw& copy(free_throw& ft)
{
free_throw *pt; // cp是一个指针变量
*pt = ft; // pt指向ft
return *pt; // 返回传入参数的引用
}
2. 使用new分配新的内存空间,返回内存的地址
但是这种方法隐含了对new()的调用,使得很容易忘记使用delete释放内存
但是可以使用智能指针来解决这种隐含的情况:
函数重载:
函数签名,函数特征标(function signature), 函数参数列表
函数重载中需要注意的条目:
1. 编译器将类型的引用和类型本身视为同一个特征标:
例如:
double cube(double x);
double cube(double& x); // invalid
例如上面的情形就是不合法的函数重载
2. 函数匹配时,将不区分const和非const变量
// const与非const的区别
const char p1[20] = "Hello the piano";
char p2[20] = "Hello the macro";
//函数重载
void dribble(char* p);
void dribble(const char* p); // 函数重载
void dabble(char* bits);
void drive(const char* p);
dribble(p1); // 匹配const参数的函数
dribble(p2); // 匹配非const参数的函数
// 非const赋给const是合法的
// const赋给非const是不合法的
dabble(p1); // invalid 参数不匹配
drive(p2); // 参数匹配
名称修饰:
c++通过名称修饰来跟踪每一个重载函数。他根据函数型原型中指定的形参类型对每个函数名进行加密。
例如:
long myFunc(int, float)被编译器加密为?myFunc@@YAXH
函数模板:
函数模板是通用的函数描述,它使用泛型来定义函数,其中的泛型可以用具体的类型来替代。由于类型是用参数表示的,因此模板特性也被称为参数化类型。
实际上,模板并不创建函数,而只是告诉编译器如何定义函数。在实际使用时只需要按照实际传入的参数创建函数。
重载的模板:
可以向像重载常规函数那样重载模板定义。
模板的局限性:
某一些类型的操作可能没有定义,例如,对于结构体,没有+-*/的操作。
解决方案:
1. c++允许对一些运算符进行重载,重载运算符即可
2. 为特定类型提供具体化的模板定义
具体化机制介绍:
第三代具体化:(c++98 具体化方法)
1. 对于给定的函数名,可以有非模板函数,模板函数,显示具体化模板函数以及他们的重载
2. 显式具体化的原型和定义以template< >开头,并通过名称来指出类型。
3. 具体化优先于常规模板,非模板函数优先于具体化和常规模板, 即 非模板函数 > 显式具体化 > 模板函数
举一个具体的例子:
// no template function prototype
void swap(job& a, job& b);
// template function prototype
template <typename T>
void swap(T& a, T& b);
// explicit speciallization 显式具体化
template <> void swap<job>(job& a, job& b);
例如,要实现swap函数,对两个结构体进行交换操作:
#include <iostream>
using namespace std;
// template function prototype
template <typename T>
void swap(T& a, T& b);
struct job
{
char name[40];
double salary;
int floor;
};
// explicit speciallization 显式具体化
template <> void swap<job>(job& a, job& b);
int main()
{
cout.precision(2); // 设置输出精度
cout.setf(ios::fixed, ios::floatfield);
int n1 = 10;
int n2 = 20;
cout << "n1 =" << n1 << " n2 " << n2 << endl;
swap(n1, n2);
cout << "n1 =" << n1 << " n2 " << n2 << endl;
p1 = {"zhangsna",
45.36,
89
} ;
p2 = {
"wangwu",
45.3,
90
}
swap(p1, p2);
return 0;
}
// function defination
template<typename T>
void swap(T& a, T&b)
{
T temp;
temp = a;
a = b;
b = temp; // 结构体允许这样做
}
template <> void swap<job>(job& a, job& b)
{
double temp1;
int temp2;
temp1 = a.salary;
a.salary = b.salary;
b.salary = temp1;
temp2 = a.floor;
a.floor = b.floor;
b.floor = temp2;
}
函数模板的发展:
模板带来的问题:
1. 在编写模板时,并非总能知道在声明中使用哪种类型:
例如:
template<typename T1, typename T2>
void ft(T1 x, T2 y)
{
...
?type? sum = x+y;
....
}
那么sum因该是什么类型呢,这个与传入的参数的类型是有关的。因此在c++98中没有办法声明sum的类型。
2.decltype关键字
c++11通过decltype关键字来提供解决方案
int x;
decltype(x) y; // 定义y的类型与x相同
所以,上面的函数可以改写为:
template<typename T1, typename T2>
void ft(T1 x, T2 y)
{
...
decltype(x+y) sum;
sum = x+y;
....
}
decltype的实际运作原理:
假设有如下声明:
decltype(expression) var;
为了确定类型,编译器会遍历一个核对表:
第一步: 如果expression是一个没有用括号括起来的标识符,和var的类型和标识符的类型相同,包括const等限定符
例如:
double x = 9.0;
double y = 7.9;
double& rx = x;
const double* pd;
decltype(x) w; // w double
decltype(rx) u = y; // u double&
decltype(pd) v; // v double*
第二步:如果expression是函数调用,则var的类型和函数返回值的类型相同
long indeed(int);
decltype (indeed(3)) y; // y long
这一操作并不会实际调用函数,编译器只是通过查看函数的原型来获取返回值的类型。
第三步:如果expression为一个左值,则var为其引用类型
例如:
double xx = 4.4;
decltype ((xx)) r2 = xx; // r2 double&
decltype (xx) w = xx; // w double
第四步:如果前面的都不满足,则var的类型与expression的类型相同
int j = 4;
int& m = j;
int& n = j;
decltype(j+6) w; // w int
decltype(m+n) u; // u int; 虽然mn都是引用,但是m+n是两个int的和,所以u的类型为int
同时,如果需要多次申明,可结合和typedef使用:
template<typename T1, typename T2>
void ft(T1 x, T2 y)
{
...
typedef decltype(x+y) xytype;
xytype sum;
xytype a1;
sum = x+y;
....
}
decltype无法解决的问题:
具有返回值的模板函数:
例如:
template<typename T1, typename T2>
?type? gt(T1 x, T2 x)
{
...
return x+y;
...
}
无法预先知道参数x,y的类型,所以返回值的类型无法确定,此时还不能使用decltype(x+y)来说明返回值类型,因为此时参数x,y还不再作用域内,必须在声明参数后使用decltype:
C++提供了后置返回类型来解决这种问题:
使用auto:
auto h(int x, float y)->double
{
...
//function body;
...
}
auto在这里相当于一个占位符,表示后置返回类型提供的类型。
所以上述问题的解决方案可以为:
template<typename T1, typename T2>
auto gt(T1 x, T2 x) -> decltype(x+y)
{
...
return x+y;
...
}
现在参数x,y位于作用域内,所以可以使用它们。
----------------------------------------------------------------------------------------------------------