文章目录
1. 内联函数
1.1 基础知识
内联函数是C++为提高程序运行速度所做的一项改进。常规函数和内联函数的主要区别在于C++编译器如何将他们组合到程序中。
- 常规函数:使用跳转地址的形式进行函数访问,函数访问结束后返回。
- 内联函数:它编译代码与其他程序内联起来。也就是说编译器将使用相应的函数代码替换函数调用,从而无需跳转。
内联函数比常规函数稍快,但是代价是需要占用更多的内存。
内联函数的使用必须采取下列措施之一:
- 在函数声明前加上关键字inline;
- 在函数定义前加上官架子inline
下面简单举例进行说明,可以看出它与常规函数的使用没什么区别。
#include<iostream>
using namespace std;
inline double square(double x)
{return x * x;}
int main()
{
double a,b;
double c = 13.0;
a = square(5.0);
b = square(4.5 + 7.5);
cout<<"a: "<<a<<endl;
cout<<"b: "<<b<<endl;
return 0;
}
输出结果:
1.2 内联函数与宏
inline工具是C++新增的特性。C语言使用预处理器语句#define来提供宏,它属于内联函数的原始实现。例如:
#define SQUARE(X) X*X
我们知道宏定义是通过本文替换来实现的,所以上面的X表示“参数”。
2. 引用变量
引用是已定义变量的别名。引用变量的主要用途是做函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是其副本。
2.1 引用变量
引用的使用:
int rats;
int &rodents = rats;
rodents将作为rats的别名。这里&不是地址运算符,而是类型标识符。就像char*一样,是指向char的指针一样。引用必须在定义的时候就将它初始化。 引用更接近于const,必须在创建的时候初始化,一旦与某个变量关联起来,就一致效忠于它。也就是说:
int &rodents = rats;
实际上面的代码块是下述代码的伪装表示:
int * const pr = &rats;
2.2 将引用用作函数参数
引用经常用作函数参数,使得函数中的变量名称为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。我们知道C语言只能进行值传递,它属于一种拷贝。
下面使用引用交换两个变量:
#include<iostream>
using namespace std;
void swap(int& a, int& b);
int main()
{
int wallet1 = 300;
int wallet2 = 350;
cout<<"wallet1: "<<wallet1<<endl;
cout<<"wallet2: "<<wallet2<<endl;
swap(wallet1,wallet2);
cout<<"After using references to swap"<<endl;
cout<<"wallet1: "<<wallet1<<endl;
cout<<"wallet2: "<<wallet2<<endl;
return 0;
}
void swap(int& a, int& b)
{
int temp;
temp = a;
a = b;
b = temp;
}
输出:
如果想要使用引用,但是又不想在调用的函数中修改变量的值,那么可以在形参的前面加const。
double refcube(const double &ra);//ra将不会被改变。
如果传递的参数比较简单,使用按值传递就可以了,但是如果传递的数据比较大(如结构和类)时,引用参数将很有用,因为引用是一种别名,而不是拷贝。
2.3 将引用用于结构
引用非常适合用于结构和类,而且引用的引入主要是为了用于这些类型的。
参考代码:
#include<iostream>
#include<string>
using namespace std;
struct free_throws
{
string name;
int made;
int attempts;
float percent;
};
void display(const free_throws& ft);
void set_pc(free_throws &ft);
free_throws& accumulate(free_throws& target, const free_throws& source);
int main()
{
free_throws one = {"Ifelsa Brance", 13, 14};
free_throws two = {"Andor Knott", 10, 16};
free_throws team = {"Throwgoods", 0, 0};
set_pc(one);
display(one);
accumulate(team, one);
display(accumulate(team, two));
accumulate(accumulate(team, one), two);
return 0;
}
void display(const free_throws& ft)
{
cout<<"Name: "<<ft.name<<endl;
cout<<"Made: "<<ft.made<<endl;
cout<<"Attempts; "<<ft.attempts<<endl;
cout<<"Percent: "<<ft.percent<<endl;
}
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)
{
target.attempts += source.attempts;
target.made += source.made;
set_pc(target);
return target;
}
输出:
上面的程序中,accumulate返回的是引用,它返回的是target的对象。如果不是引用则返回拷贝,意味着需要重新拷贝一个临时位置,然后在拷贝到需要的位置去。
2.4 将引用用于类对象
这里以string对象举例:
#include<iostream>
#include<string>
using namespace std;
string version1(const string& s1,const string& s2);
const string& version2(string& s1, const string& s2);
const string& version3(string& s1, const string& s3);
int main()
{
string input;
string copy;
string result;
cout<<"Enter a string: ";
getline(cin, input);
copy = input;
cout<<"Your string as entered: "<<input<<endl;
result = version1(input,"***");
cout<<"result: "<<result<<endl;
cout<<"Original string: "<<input<<endl;
result = version2(input,"###");
cout<<"result: "<<result<<endl;
cout<<"Original: "<<input<<endl;
result = version3(input,"@@@");
cout<<"result: "<<result<<endl;
cout<<"Original: "<<input<<endl;
return 0;
}
string version1(const string &s1, const string &s2)
{
string temp;
temp = s2 + s1 + s2;
return temp;
}
const string& version2(string& s1,const string& s2)
{
s1 = s2 + s1 + s2;//s1是引用传递,它的值会被改变
return s1;
}
const string& version3(string& s1,const string& s2)
{
string temp;
temp = s2 + s1 + s2;
return temp;//temp是临时变量,如果返回它的引用不可行!!!
//程序崩溃
}
version3不会被被正常执行,因为它返回的是一个临时变量,临时变量在函数结束后将被销毁,而函数的返回值类型为引用。所以它的输出为:
2.5 对象、继承和引用
ostream是基类,ofstream是派生类,这意味着ofstream对象可以使用基类的特性,如格式化方法percision()和setf()。
基类的另一个特性,基类引用可以指向派生类,而无需进行强制类型转换。例如,参数类型ostream&的函数可以接受ostream对象或您声明的ofstream对象作为参数。
参考代码等复习文件的时候补上!
2.6 何时使用引用参数
引用参数有两个主要原因
第一:程序员能够修改调用函数中的数据对象。
第二:通过传递引用而不是整个数据对象,可以提高程序的运行速度。
这些同样是使用指针参数的原因。因为引用参数实际上是基于指针的代码的另一个接口。
关于何时使用引用,下面有一些指导原则。
对于使用传递的值不作修改的函数:
- 如果数据对象很小,用值传递;
- 如果数据类型是数组,则用指针,且将指针申明为const指针。
- 如果数据对象较大,则使用const指针或const引用;来提高效率。
- 如果数据对象是类对象,则使用const引用。
对于修改调用函数中数据的函数:
- 如果数据对象很小,则使用指针。
- 如果数据对象是数组,则只能使用指针。
- 如果数据对象是结构,则使用引用或指针
- 如果数据对象是类对象,则使用引用。
3. 默认参数
默认参数必须通过函数原型告知程序。因为编译器通过查看原型来了解函数所使用的的参数数目,而和函数定义无关。比如:
char * left(const char* str, int n = 1);
对于形参列表的函数原型,必须从右向左添加默认值。这样函数调用的时候就方便设置值了!
int harpo(int n,int m= = 4,int j = 5);//有效
int chico(int n, int m = 6, int j);//无效
int groucho(int k = 1, int m = 2, int n = 3);//有效
对于harpo函数,我们可以提供1个、2个或3个参数,实参从左至右赋值给形参:
beeps = harpo(2);//harpo(2, 4, 5);
beeps = harpo(1, 8);//harpo(1, 8, 5);
beeps = harpo(8, 7, 6);//没有默认参数
程序举例:
#include<iostream>
using namespace std;
const int ArSize = 80;
char* left(const char* str,int n = 1);
int main()
{
char sample[ArSize];
cout<<"Enter a string:\n";
cin.get(sample, ArSize);
char *ps = left(sample, 4);//只要前4个字符
cout<<ps<<endl;
delete [] ps;
ps = left(sample);//使用默认,只要第一个字符
cout<<ps<<endl;
delete [] ps;
return 0;
}
char* left(const char *str,int n)
{
if(n < 0) n = 0;
char*p = new char[n+1];
int i = 0;
for(i = 0;i < n&&str[i];i++)//str[i]防止n输入超出范围
p[i] = str[i];
while(i <= n)
p[i++] = '\0';
return p;
}
4. 函数重载(多态)
4.1 概念
函数多态是C++在C语言的基础上新增的函数功能,它可以使程序使用多个同名的函数。函数重载的关键是函数的参数列表——也称为函数特征标。如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名无关紧要。C++允许定义名称相同的函数,条件是它们的特征标不同。
下列两个函数不能进行重载,因为对于编译器来说,无法判断使用哪个。
double cube(double x);
double cube(double &x);//这两个不能进行函数重载
下列函数也不能进行重载,返回值不同,不能作为重载的标志。
long gronk(int n, float m);
double gronk(int n, float m);//编译器无法判断调用哪个
如果都是引用类型,它属于函数重载,根据函数调用的匹配程度来决定调用哪个。
#include<iostream>
using namespace std;
void stove(double& r1);
void stove(const double & r2);
void stove(double && r3);
int main()
{
double x = 5.5;
const double y = 32.0;
stove(x);//r1
stove(y);//r2
stove(x + y);//r3
}
void stove(double &r1)
{
cout<<r1<<endl;
}
void stove(const double &r2)
{
cout<<r2<<endl;
}
void stove(double &&r3)
{
cout<<r3<<endl;
}
输出:
4.2 一个示例
#include<iostream>
using namespace std;
unsigned long left(unsigned long num, unsigned ct);
char* left(const char* str,int n = 1);
int main()
{
char* trip = "Hawaii!!!";
unsigned long n = 12345678;
int i;
char * temp;
for(i = 1; i < 10; i++)
{
cout<<left(n,i)<<endl;
temp = left(trip, i);
cout<<temp<<endl;
delete [] temp;
}
return 0;
}
unsigned long left(unsigned long num, unsigned ct)
{
unsigned digits = 1;
unsigned long n = num;
if(ct == 0 || num == 0)return 0;
while (n/=10)digits++;
if(digits>ct)
{
ct = digits - ct;
while(ct--)num/=10;
return num;
}
return num;
}
char* left(const char* str, int n)
{
if(n<0)n=0;
char*p = new char[n+1];
int i;
for(i=0; i<n && str[i];i++)
p[i] = str[i];
while(i <=n)p[i++]='\0';
return p;
}
输出:
5. 函数模板
5.1 定义
函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中泛型可用具体的类型(int或double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。有时候也称通用编程,模板特性有时候也称为参数化类型。
比如一个交换函数的函数模板:
template <typename T>
void Swap(T &a, T &b);
这里的T可以换成任意变量形式,它只是一个符号形式。
关键字typename也可换成class,它表示类型。
下面看看它的具体使用:
#include<iostream>
using namespace std;
template <typename T> //or class T
void Swap(T &a, T &b);
int main()
{
int i = 10,j = 20;
cout<<"i, j = "<<i<<","<<j<<endl;
cout<<"After Swap(i,j):\n";
Swap(i,j);//这里为int版本
cout<<"i, j = "<<i<<","<<j<<endl;
return 0;
}
template<typename T>
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
输出:
使用模板并不能缩短可执行程序。调用多少个类型,就会生成多少个函数,它只是使得生成多个函数的定义更简单、更可靠。
5.2 重载模板
并不是所有类型都是用相同模板的算法,所以我们可以对模板进行重载,以满足不同的需求。下列代码有两个交换模板,一个可以交换常规变量,一个用于交换数组类型。
#include<iostream>
using namespace std;
template <typename T> //第一个模板,交换常规变量
void Swap(T &a, T &b);
template <typename T>//重载模板,交换数组类型
void Swap(T *a, T*b, int n);
void Show(int a[]);
const int Lim = 8;
int main()
{
int i = 10,j = 20;
cout<<"i, j = "<<i<<","<<j<<endl;
cout<<"After Swap(i,j):\n";
Swap(i,j);//这里为int版本
cout<<"i, j = "<<i<<","<<j<<endl;
int d1[Lim] = {0, 7, 0, 4, 1, 7, 7, 6};
int d2[Lim] = {1, 2, 3, 4, 5, 6, 7, 8};
cout<<"Original arrray:\n";
Show(d1);
Show(d2);
Swap(d1, d2, Lim);//数组类型
cout<<"After Swap array:\n";
Show(d1);
Show(d2);
return 0;
}
template <typename T>
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
template <typename T>
void Swap(T *a,T *b, int n)
{
T temp;
for(int i = 0; i < n; i++)
{
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
void Show(int a[])
{
for(int i = 0; i < Lim; i++)
cout<<a[i]<<" ";
cout<<endl;
}
输出:
5.3 显示具体化和显示实例化
加入显示具体化之后,函数就有三种类型:非模板函数、模板函数和具体化的原型,他们被调用的优先级为:
非模板函数 > 显示具体化模板函数 > 模板函数
下列分别非三种类型的原型,可以不指明变量名。
void Swap(job &,job &);//非模板函数,不同函数
template <typename T>//模板函数
void Swap(T &, T &);
template<> void Swap<job>(job &,job &);//显示具体化模板函数
//其中具体化为job类型
template<> void Swap(job &, job &);//显示具体化模板函数,第二种方法
注意与显示实例化的区别,显示实例化指在程序执行过程,显示的表明生成的函数类型。如下面的Add函数,需要手动确定调用哪一种类型。
template <class T>
T Add(T a, T b)
{return a + b;}
int m = 6;
double x = 10.2;
Add<double>(x, m);//m为int型,需要显示实例化,这里利用了强制转换。
隐式实例化、显示实例化和显示具体化统称为具体化。它们的相同之处在于,都是用具体类型的函数定义,而不是通用描述。
上一例中的Add之所以可以运行,是因为可以进行强制类型转换,但是如果使用引用类型,则行不通(没有进行声明)。为了区分显示实例化,使用显示具体化的时候需要进行声明(手动确定使用哪一种类型),它的声明方式和调用方式如下:
template void Swap<char>(char&, char&);//显示实例化声明,将使用char类型
char a,b;
Swap(a, b);//显示实例化调用
函数重载、函数模板和函数模板重载的出现,导致出现很多种类型的函数,这就涉及到函数的调用选择,设计的内容太多,感兴趣可以查看书中的8.5.5章节。
5.4 decltype结合auto的使用
C++11新增关键字decltype,可以指明变量类型:
int x;
decltype (x) y;//y的类型和x类型相同,也就是int类型
对于函数模板声明,如果无法知道函数的返回类型,可以使用auto进行占位,具体调用的时候再确定返回类型。
template <class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x + y)
{
return x + y;
}
总览目录
上一篇:(五)函数的使用
下一篇:(七)内存模型和名称空间
文章参考:《C++ Primer Plus第六版》