C++ 函数探幽(七)上

it2024-01-24  71

1.C++内联函数

内联函数是C++为提高程序运行速度所做的一项改进。C++内联函数的编译代码与其他程序代码“内联”起来了。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多的内存,如果程序在10个不同的地方调用同一个内联函数,则该程序将包含该函数代码的10个副本

要使用这项特性,必须采取下述措施之一: ⭐在函数声明前加上关键字inline; ⭐在函数定义前加上关键字inline。

程序员请求将函数作为内联函数时,编译器不一定会满足这种要求。它可能认为该函数过大或注意到函数调用了自己(内联函数不能递归),因此不能将其作为内联函数;而有些编译器没有启用或实现这种特性。

内联与宏

inline工具是C++新增的特性。C语言使用预处理器语句#define来提供宏—内联代码的原始实现。例如下面是一个计算平方的宏

#define SQUARE(X) X*X a = SQUARE(5.0);//a = 5.0 *5.0 b = SQUARE(4.5 + 7.5);// b = 4.5+7.5*4.5+7.5 SQUARE(c++);//c= C++*C++

可以通过括号来改进

#define SQUARE(X) (X) * (X)

但会发现SQUARE(c++)仍递将c递增两次,这里可以看出C++的内联功能远远胜过C语言的宏定义

2.引用变量 C++新增了一种复合类型-引用变量 C和C++使用&符号来指示变量的地址。C++给&符号赋予了另一个含义,将其用来声明引用。例如rodents作为rats变量的别名

int rats; int &rodents = rats;

这里&不是地址运算符,int&指的是指向int的引用,上述引用声明允许将rats和rodents互换-他们指向相同的值和内存单元

这样表达式rodents和*p可以同rat互换,而表达式&rodents和p都可以同&rats互换。从这一点来看,引用看上去很像伪装指针。实际上引用还是不同于指针的,除了表示法不同外,还有其他的差别 ,差别之一是必须在声明引用时,将其初始化,而不能像指针那样,先声明再赋值:

int rat; int &rodent;//invalid rodent = rat;//invalid

引用更接近const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就将一直效忠于它。也就是说:

int &rodents = rats; //实际上是下述代码的伪装表示 int *const pr = &rats;

下面试图将rats变量的引用改为mouse变量

值变了,但rodents地址和mouse不相同,因为rodents是rats的别名,因此上述赋值语句与下面的语句等价

rats = mouse;

但有程序会试图这么修改 将引用用作函数参数 引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方法称为引用传递。按引用传递允许被调用的函数能够访问调用函数中的变量。C++新增的这项特性是对C语言的超越,C语言只能按值传递.按值传递导致被调用函数使用调用程序的值的拷贝。当然C语言也允许避开按值传递的限制,采用按指针传递的方式

依旧是以交换两个变量的值做例子

#include <iostream> #include <stdio.h> using namespace std; void ref_swap(int &a, int &b); void point_swap(int *p, int *q); void value_swap(int a, int b); int main() { using namespace std; int wallet1 = 300; int wallet2 = 350; cout << "wallet = $" << wallet1; cout << " wallet = $" << wallet2 << endl; cout << "using references to swap contents:\n"; ref_swap(wallet1, wallet2); cout << "wallet1 = $" << wallet1; cout << " wallet2 = $" << wallet2 << endl; cout << "using pointers to swap contents again:\n"; point_swap(&wallet1, &wallet2); cout << "wallet1=$" << wallet1; cout << " wallet2=$" << wallet2 << endl; cout << "Trying to use passing by value:\n"; value_swap(wallet1, wallet2); cout << "wallet1 = $" << wallet1; cout << " wallet2 = $" << wallet2 << endl; system("pause"); return 0; } void ref_swap(int &a, int &b) { int temp; temp = a; a = b; b = temp; } void point_swap(int *p, int *q) { int temp; temp = *p; *p = *q; *q = temp; } void value_swap(int a, int b) { int temp; temp = a; a = b; b = temp; }

按引用传递和按值传递看起来很相似。但其实有内在区别:按引用传递中变量a和b是wallet1和wallet2的别名,所以交换了a和b的值相当于交换wallet1和wallet2的值;但按值传递中,变量a和b是复制了wallet1和wallet2的值的新变量,因此交换a和b的值并不会影响wallet1和wallet2的值

计算参数的立方

#include <iostream> double cube(double a); double ref_cube(double &ra); int main() { using namespace std; double x = 5.0; cout << cube(x); cout << " = cube of " << x << endl; cout << ref_cube(x); cout << " = cube of " << x << endl; system("pause"); return 0; } double cube(double a) { a *= a * a; return a; } double ref_cube(double &ra) { ra *= ra * ra; return ra; }

ref_cube()函数修改了main()中的x值,而cube()没有,这提醒我们为何通常按值传递。变量a位于cube()中,它被初始化为x的值,但修改a并不会影响x。

double z = ref_cube(x+3.0)//这样的代码是不合理 x+3.0=5.0//例如,不能将值赋给该表达式

临时变量、引用参数和const

如果实参与引用参数不匹配,C++将生成临时变量。当前,仅当参数为const引用时,C++才允许这样做

如果引用参数是const,则编译器将在下面两种情况下生成临时变量: ⭐实参的类型正确,但不是左值 ⭐实参的类型不正确,但可以转换为正确的类型

左值:左值参数是可被引用的数据对象,例如,变量、数组元素、结构成员、引用和解除引用的指针都是左值。在C语言中,左值最初指的是可出现在赋值语句左边的实体,但这是引入关键字const之前的情况。现在常规变量和const变量都可被视为左值,因为可通过地址访问它们。但常规变量属于可修改的左值,而const变量属于不可修改的左值

回到前面的示例,假设重新定义为以下:

#include <iostream> double cube(double a); double ref_cube(const double &ra); int main() { using namespace std; double x = 5.0; long edge = 5L; cout << cube(x); cout << " = cube of " << x << endl; cout << ref_cube(x + 10.0); cout << " = cube of " << x << endl; cout << ref_cube(edge); cout << " = cube of " << edge << endl; cout << ref_cube(7.0) << endl; system("pause"); return 0; } double cube(double a) { a *= a * a; return a; } double ref_cube(const double &ra) { return ra * ra * ra; }

edge虽然是变量,类型却不正确,double引用不能指向long。另一方面,参数7.0和x+10.0的类型都正确,但没有名称,在这些情况下,编译器都将生成一个临时匿名变量,并让ra指向它。这些临时变量只在函数调用期间存在,此后编译器便可以随意将其删除

long a = 5; long b = 5; ref_swap(a, b); void ref_swap(const int &a,const int &b) { int temp; temp = a; a = b; b = temp; }

早期C++较宽松的规则下类型不匹配会创建两个int临时变量,所以a和b的值没有发生改变,现在的C++标准会阻止这种修改作为参数传递的原始变量的操作,禁止创建临时变量

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

但是从ref_cube()函数来看,该函数的目的只是使用传递的值,而不是修改它们,因此临时变量不会造成任何不利的影响,反而会使函数在可处理的参数种类方面更通用

应尽可能使用const 将引用参数声明为常数数据的引用的理由有三个: ⭐ 使用const可以避免无意中修改数据的编程错误 ⭐ 使用const使函数能够处理const 和 非 const 实参,否则将只能接受非const数据 ⭐ 使用const 引用使函数能够正确生成并使用临时变量

因此应尽可能将引用参数声明为const C++11新增了另一种引用-右值引用(rvalue reference).这种引用可指向右值,是使用&&声明的:

新增右值引用的主要目的是,让库设计人员能够提供有些操作的更有效的实现

将引用用于结构

#include <iostream> #include <stdio.h> #include <string> struct free_throws { std::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 = {"zhoumouren", 13, 14}; free_throws two = {"caimouren ", 10, 16}; free_throws three = {"Minnie Max", 7, 9}; free_throws four = {"whily Looper", 5, 9}; free_throws five = {"Long long", 6, 14}; free_throws team = {"Throwgood", 0, 0}; free_throws dup; set_pc(one); display(one); accumulate(team, two); display(team); display(accumulate(team, three), four); display(team); dup = accumulate(team, five); std::cout << "Displaying team:\n"; display(team); std::cout << "Displaying dup after assignment:\n"; display(dup); set_pc(four); accumulate(dup, five) = four; std::cout << "Displaying dup after ill-advised assignment:\n"; display(dup); system("pause"); return 0; } void display(const free_throws &ft) { using std::cout; cout << "Name: " << ft.name << '\n'; cout << " Made:" << ft.made << '\t'; cout << "Attempts:" << ft.attempts << '\t'; cout << "Percent" << ft.percent << '\n'; } 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; }

为何要返回引用

先看下面一段代码

double m = sqrt(16.0); cout << sqrt(25.0);

在第一条语句中,值4.0被复制到一个临时位置,然后被复制给m。第二条语句,值5.0被复制到一个临时位置,然后传递给cout

再来看下面一条语句

dup = accumulate(team,five);

如果accumulate()返回一个结构,而不是指向结构的引用,将把整个结构复制到一个临时位置,再将这个拷贝复制给dup。但在这返回值为引用时,将直接把team复制到dup上,其效率更高

将引用用于类对象

#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 &s2); 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 << "Your string enhanced:" << result << endl; cout << "Your original string:" << input << endl; result = version2(input, "###"); cout << "Your string enhanced: " << result << endl; cout << "Your original string:" << input << endl; cout << "Resetting original string.\n"; input = copy; result = version3(input, "@@@"); cout << "Your string enhanced:" << result << endl; cout << "Your original string:" << 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; return s1; } const string &version3(string &s1, const string &s2) { string temp; temp = s2 + s1 + s2; return temp; }

此时程序崩溃

temp是一个新的string对象,返回指向temp的引用是不可行的。

3.何时使用引用参数

使用引用参数的主要原因有两个 ⭐程序员能够修改调用函数中的数据对象 ⭐通过传递引用而不是整个数据对象,可以提高程序的运行速度

什么时候应使用引用、什么时候应使用指针呢?什么时候应按值传递呢?下面是一些指导原则: 对于使用传递的值而不作修改的函数 ⭐如果数据对象很小,如内置数据类型或小型结构,则按值传递 ⭐如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针 ⭐如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率.这样可以节省复制结构所需的时间和空间 ⭐如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此传递类对象参数的标准方式是按引用传递

对于修改函数中数据的函数: ⭐如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码(其中x是int),则很明显,该函数将修改x ⭐如果数据对象是数组,则只能使用指针 ⭐如果数据对象是结构,则使用引用或指针 ⭐如果数据对象是类对象,则使用引用

当然这只是一些指导原则,很可能有充分的理由做出其他的选择

———————————————————————上一篇:C++ 函数——C++的编程模块(六)下

———————————————————————下一篇:C++ 函数探幽(七)下

最新回复(0)