inline
;②在函数定义前加上关键字inline
#include
inline double square(double x){return x * x;}
int main(){
using namespace std;
double a, b;
double c = 13.0;
a = square(5.0); //25
b = square(4.5 + 7.5); //144
cout<<"a = "<<a<<", b = "<<b<<"\n";
cout<<"c = "<<c<<"\n";
cout<< ", c squared = "<<square(c++) << "\n"; //169
cout<<"Now c = "<<c<<"\n"; //14
return 0;
}
示例说明:①尽管程序没有提供独立的原型,但C++原型特性仍在起作用,这是因为在函数首次使用前出现的整个函数定义充当了原型;②内联与宏:C语言的宏也能实现类似函数的功能,但最好使用内联来代替它们,因为宏定义只是进行了文本替换,且宏定义的"函数"其参数不适合用自增或自减符号引用变量的创建示例:int rats; int & rodents = rat;
,引用与指针有很多相似之处,但是引用必须要在声明时进行初始化,而指针变量不必。引用更接近const指针,因此引用变量的创建是下述代码的伪装表示:int * const rodents = &rats
引用一旦声明,就不能通过赋值来设置了,如以下代码所示。即便pt的指向有所改变,但并不能改变这样的事实,即rodents
引用的是rats
。
int rats = 101;
int * pt = &rats;
int & rodents = *pt;
int bunnies = 50;
pt = &bunnies;
C语言只能按值传递
,但C++因为引入了引用这一概念,因此可以将引用作为函数参数,使得函数中的变量名成为调用程序中的变量的别名,即按引用传递
左值的意思是可以被引用的数据对象(如变量、数组元素、结构成员、引用和解除引用的指针),非左值包括字面常量(用引号括起的字符串除外,它们由其地址表示)和包含多项的表达式
如果实参与引用参数不匹配,C++将生成临时变量。当前,仅当参数为const引用时,C++才允许这样做,但以前不是这样。创建临时变量的条件(引用参数为const时):①实参的类型正确,但不是左值;②实参的类型不正确,但可以转换为正确的类型。临时变量的有关示例如下。
double refcube(const double& ra) {return ra * ra * ra;}
int main(){
double 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
return 0;
}
上述示例中,edge虽然是变量,类型却不正确,double引用不能指向long;另一方面,参数7.0和side + 10.0的类型都正确,但没有名称。在这些情况下,编译器都将生成一个临时匿名变量,并让ra指向它。这些临时变量只在函数调用期间存在,此后编译器便可以随意将其删除。
临时变量必须是对于常量引用才会生成,原因通过如下示例说明。
void swapr(int& a, int& b){ int temp; temp = a; a = b; b = temp;}
int main(){
long a = 3, b = 5;
swapr(a, b);
return 0;
}
在早期C++较宽松的规则下,执行以上代码时,由于实参类型为long,与double不匹配,因此编译器将创建两个临时变量int,将它们初始化为3和5,然后交换临时变量的内容,而a和b保持不变。
简而言之,如果接受引用参数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现。解决方法是,禁止创建临时变量,现在的C++标准正是这样做的。现在来看上上个示例中的refcube()函数,该函数的目的只是使用传递的值,而不是修改它们,因此临时变量不会造成任何不利的影响,反而会使函数在可处理的参数种类方面更通用。因此,如果声明将引用指定为const,C++将在必要时生成临时变量。实际上,对于形参为const引用的C++函数,如果实参不匹配,则其行为类似按值传递,为确保原始数据不被修改,将使用临时变量来存储值。
注意:如果函数调用不为左值或与相应的const引用参数的类型不匹配,则C++将创建类型正确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量
将引用参数尽可能声明为常量数据的引用的理由:①使用const可以避免无意中修改数据的编程错误;②使用const使函数能够处理const和非const实参,否则将只能接受非const数据;③使用const引用使函数能够正确生成并使用临时变量
C++11新增了右值引用
,这种引用可以指向右值,如以下示例所示。引入右值引用的主要目的:让库设计人员能够提供有些操作的更有效实现。第18章将讨论如何使用右值引用来实现移动语义(move semantics)
double && rref = std::sqrt(36,00);
double j = 15.0;
double && jref = 2,0 * j + 18.5;
std::cout<<rref<<'\n'; // 6,0
std::cout<<jref<<'\n'; //48.5
函数返回类型为引用,这种情况下,函数处于赋值等号左边是可行的,这是因为返回引用的函数实际上是被引用的变量的别名
返回引用时最重要的一点是,应避免返回函数终止时不再存在的内存单元引用。 为避免这种问题,①返回一个作为参数传递给函数的引用,这样,作为参数的引用将指向调用函数使用的数据;②用new来分配新的存储空间,但这种方法有一个隐患,即容易忘记使用delete来释放这些存储空间,第16章讨论的auto_ptr模板以及C++11新增的unique_ptr可帮助程序员自动完成释放工作
将const用于引用返回类型的原因:①避免返回引用的函数作为左值被修改,仅供使用该引用返回值;②避免在设计中添加模糊的特性,因为模糊的特性增加了犯错的机会。然而,有时候省略const确实有道理,第11章将讨论的重载运算符<<就是一个这样的例子
有关函数返回引用的示例。
#include
#include
using namespace std;
struct free_throws{
string name;
int made;
int attempts;
float percent;
};
const free_throws& clone(free_throws& ft){
free_throws* pt = new free_throws;
*pt = ft;
return *pt;
}
void show(const free_throws& ft){
cout<<"Name:"<<ft.name<<endl;
cout<<"made:"<<ft.made<<endl;
cout<<"Attempts:"<<ft.attempts<<endl;
cout<<"Percent:"<<ft.percent<<endl;
}
int main(){
free_throws three{"Minnie Max", 7, 9};
show(three);
const free_throws& jolly = clone(three);
show(jolly);
cout<<"Now change three\nthree:\n";
three.percent = 1.0 * three.made / three.attempts;
show(three);
cout<<"jolly"<<endl;
show(jolly);
delete &jolly; //easy to forget this
return 0;
}
假设实参的类型与引用参数类型不匹配,但可被转换为引用类型,程序将创建一个正确类型的临时变量,使用转换后的实参值来初始化它,然后传递一个指向该临时变量的引用
对象、继承和引用:ostream
为基类,ofstream
是派生类。一个特性:基类引用可以指向派生类对象。有一个关于该特性演示的示例如下所示。
void file_it(ostream& os, double fo, const double fe[], int n){
ios_base::fmtflags initial;
initial = os.setf(ios_base::fixed); //save initial formatting state
os.precision(0);
os<<"Focal length of objective:"<<fo<<" mm\n";
os.setf(ios::showpoint); //显示小数点模式
os.precision(1); //制定显示小数的位数
os.width(12); //设置下一次输出操作使用的字段宽度
os<<"f.1. eyepiece";
os.width(15);
os<<"magnification"<<endl;
for (int i=0; i<n; ++i){
os.width(12);
os<<fe[i];
os.width(15);
os<<int(fo/fe[i] + 0.5)<<endl; //四舍五入
}
os.setf(initial); //恢复格式初始状态
}
const int LIMIT = 5;
//调用
int main(){
ofstream fout;
double objective;
double eps[LIMIT];
//...
file_it(fout, objective, eps, LIMIT);
file_it(cout, objective, eps, LIMIT);
return 0;
}
代码说明:①width()
设置下一次输出操作使用的字段宽度,这种设置只在显示下一个值时有效,然后将恢复到默认设置。默认的字段宽度为零;②setf(ios_base::fixed)
、precision()
这些设置都将一直保持不变,直到再次调用相应的方法重新设置它们
使用引用参数的指导原则:
对于使用传递的值而不作修改的函数。
对于修改调用函数中数据的函数
当然,以上只是一些指导原则,很可能有充分的理由做出其他的选择。例如,对于基本类型,cin使用引用,因此可以使用cin>>n;
,而不是cin>>&n;
char* left(const char* str, int n = 1);
;相应函数定义:char* left(const char* str, int n){...}
template
或template
都可以,typename
与class
等价。如果不考虑向后兼容的问题,并愿意键入较长的单词,则声明参数时,应使用关键字typename
而不使用class
并非所有的模板参数都必须是模板参数类型
模板常置于头文件中,并在需要使用模板的文件中包含头文件
实例化与具体化:①模板并非函数定义,但使用int的模板实例是函数定义。这种实例化方式称为隐式实例化(implicit instantiation)。现在也可以显示实例化:template void Swap
②显示具体化的两种等价声明:template<> void Swap
或template<> void Swap(int&, int&);
③警告:试图在同一文件(或转换单元)中使用同一种类的显式实例化和显式具体化将出错;
重载解析(overloading resolution)过程:
编译器必须确定哪个可行函数是最佳的,由最佳到最差的顺序如下:
const和非const之间的区别只适用于指针和引用指向的数据。示例如下,其中如果只定义了#1和#2,则会产生二义性错误
struct blot{int a; char b[10];};
void recycle(blot); //#1
void recycle(const blot); //#2
void recycle(blot&); //#3
void recycle(const blot&); //#4
术语"最具体(most specialized)"并不一定意味着显式具体化,而是指编译器推断使用哪种类型时执行的转换最少
重载解析寻找最匹配函数的顺序
模板使用的两个个示例(习题6、7)
习题6 显示具体化
#include
#include
using namespace std;
template<typename T>
T maxn(T*, int);
template<> char* maxn<char*>(char*[],int);
const int ArrSize = 50;
int main(){
int x[6]{34, 25, 67, 4396, 777, 1557};
double a[4]{7.77, 4.396, 5.5, 2};
char* ca[5] =
{
new char[ArrSize]{'H','e','l','l','o'},
new char[ArrSize]{'e','v','e','r','y','o','n','e',',',' ','m','y'},
new char[ArrSize]{'n','a','m','e'},
new char[ArrSize]{'i','s'},
new char[ArrSize]{'F','l','o','y','d',' ','Y','o','f','i','n','d','!'}
};
cout<<"max for int: "<<maxn(x, 6)<<endl;
cout<<"max for double: "<<maxn(a, 4)<<endl;
cout<<"longest string in char* array: "<<maxn(ca, 5)<<endl;
for (int i=0; i<5; ++i)
delete[] ca[i];
return 0;
}
template<typename T>
T maxn(T* a, int n){
T max=a[0];
for (int i=1; i<n; ++i)
if(max<a[i])
max = a[i];
return max;
}
template<> char* maxn<char*>(char* ca[],int n){
char* ret = ca[0];
for (int i=1; i<n; ++i){
if(strlen(ca[i]) > strlen(ret))
ret = ca[i];
}
return ret;
}
习题7
#include
template<typename T>
T SumArray(T arr[], int n);
template<typename T>
T SumArray(T* arr[], int n);
struct debts{
char name[50];
double amount;
};
int main(){
using namespace std;
int things[6] = {13, 31, 103, 301, 310, 130};
debts mr_E[3] =
{
{"Ima Wolfe", 2400.0},
{"Ura Foxe", 1300.0},
{"Iby Stout", 1800.0}
};
double *pd[3];
for (int i=0; i<3; ++i)
pd[i] = &mr_E[i].amount;
cout<<"Showing Mr.E's sum of things: ";
cout<<SumArray(things, 6);
cout<<"\nShowing Mr.E's sum of debts: ";
cout<<SumArray(pd, 3)<<endl;
return 0;
}
template<typename T>
T SumArray(T arr[], int n){
using namespace std;
T sum = 0;
for (int i=0; i<n; ++i){
sum += arr[i];
}
return sum;
}
template<typename T>
T SumArray(T* arr[], int n){
T sum = 0;
for (int i=0; i<n; ++i){
sum += *arr[i];
}
return sum;
}
关键字decltype
(C++11):使用模板如下:decltype(expression) var;
对expression
的讨论(下文用"它"指代):
expression
是一个左值,则var指向其类型的引用。这要求expression
必须是用括号括起的标识符,示例如下double x = 7.77;
decltype(xx) r1 = xx; //r1 is double
decltype((xx)) r2 = xx; //r2 is double&
顺便说一句,括号并不会改变表达式的值与左值性。例如,下面两条语句等效:xx = 98.6;
(xx) = 98.6;
C++11后置返回类型:有时候函数返回类型不能预先确定,需要根据参数列表来确定,但通常情况下,声明完参数列表前就已经确定了返回类型,所以采用新的特性——后置返回类型来解决此问题:
template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x + y)
{
...
return x + y;
}
其中->decltype(x + y)
被称为后置返回类型(trailing return type)。其中auto
是一个占位符,表示后置返回类型提供的类型,这是C++11给auto
新增的一种角色
由于篇幅较长,加上作者水平有限,难免会有疏漏之处,望各位大佬们批评指正!