读书笔记||函数探幽

一、C++内联函数

内联函数是C++为提高程序运行速度所做的改进。常规函数和内联函数之间的主要区别不在于编写方式,而在于C++编译器如何将他们组合到程序中。
编译过程的最终产品是可执行程序。运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令。有时候循环或者分支语句,将跳过一些指令,向前或向后跳到特定的地址。常规函数调用也使程序跳到另一个函数的地址,并在函数结束时返回。执行函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈,保留内存块,跳到标记函数起点的内存单元,执行函数代码(也许还需将返回值放入寄存器中),然后跳回到地址被保存的指令处。
C++内联函数提供了另一种选择。内联函数的编译代码与其他程序代码的“内联”起来,也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多的内存。
应该有选择地使用内联函数。如果执行函数代码地时间比处理函数调用机制地时间长,则节省的时间将只占用整个过程的很小一部分。如果代码执行时间很短,则内联调用使用大部分时间。另一方面,由于这个过程相当快,因此节省了该过程的大部分时间,但节省的时间绝对值并不大,除非函数值经常被调用。
要使用这项特性,必须采取下述措施之一:
1.在函数声明前加上关键字inline;
2.在函数定义前加上关键字inline。
通常的做法是省略原型,将整个定义(即函数头和所有函数代码)放在本应提供原型的地方。

#include <iostream>
// an inline function definition
inline double square(double x) { return x * x; }
int main()
{
    using namespace std;
    double a, b;
    double c = 13.0;
    a = square(5.0);
    b = square(4.5 + 7.5);   // can pass expressions
    cout << "a = " << a << ", b = " << b << "\n";
    cout << "c = " << c;
    cout << ", c squared = " << square(c++) << "\n";
    cout << "Now c = " << c << "\n";
    // cin.get();
    return 0;  
}

在这里插入图片描述内联函数和常规函数一样,也是按值来传递的,如果参数为表达式即4.5+7.5,则函数将传递表达式的值12。这使得C++内联功能远远胜过C语言的宏定义。
尽管程序没有提供独立的原型,但C++原型特性仍在起作用。这是因为在函数首次使用前出现整个函数定义充当了原型,这意味着可以给square()传递int和long值,将值传递给函数前,程序自动将这个值强制转换为double类型。

二、引用变量

C++新增了一种复合类型——引用变量。引用是已定义的变量的别名,如果将twain作为clement变量的引用,则可以交替使用twain和clement来表示该变量。引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是其副本,这样除指针之外,引用也为函数处理大型结构提供了一种非常方便的途径,同时对于设计类来说,引用是必不可少的。
1.创建引用变量
C和C++使用&符号来指示变量的地址。C++给&符号赋予了另一个含义,将其用来声明引用。

int rats;
int & rodents = rats; //makes rodents an alias for rats

其中,&不是地址运算符,而是类型标识符的一部分。就像声明中的char*指的是指向char的指针一样,int&指的是指向int的引用。

#include <iostream>
int main()
{
    using namespace std;
    int rats = 101;
    int & rodents = rats;   // rodents is a reference
    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;
    rodents++;
    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;
// some implementations require type casting the following
// addresses to type unsigned
    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << endl;
    // cin.get();
    return 0; 
}

在这里插入图片描述
int & rodents = rats;中的&运算符不是地址运算符,而是将rodents的类型声明为int&,即指向int变量的引用,
但是, cout << ", rodents address = " << &rodents << endl;中的&的运算符是地址运算符,其中&rodents表示rodents引用的变量的地址。
在上述的例子中,rats和rodents的值和地址都相同,将rodents加1将影响这两个变量的值,更准确的讲rodents++操作讲一个有两个名称的变量加1。

 int rats = 101;
 int & rodents = rats; // rodents is a reference
 int * prats = &rats; // parts a pointer

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

int rat;
int & rodent;
rodent = rat; //No,you can't do this.
//必须在声明引用变量时进行初始化。

引用更接近const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就将一直效忠于它,也就是说:
int & rodents = rats ;
实际上是下述代码的伪装表示:
int * const pr =&rats;

#include <iostream>
int main()
{
    using namespace std;
    int rats = 101;
    int & rodents = rats;   // rodents is a reference
    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;
    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << endl;
    int bunnies = 50;
    rodents = bunnies;       // can we change the reference?
    cout << "bunnies = " << bunnies;
    cout << ", rats = " << rats;
    cout << ", rodents = " << rodents << endl;
    cout << "bunnies address = " << &bunnies;
    cout << ", rodents address = " << &rodents << endl;
    // cin.get();
    return 0; 
}

在这里插入图片描述最初,rodents引用的是rats,随后程序试图将rodents作为bunnies的引用:rodents=bunnies;
这种意图是成功的,因为rodents的值从101变为了50。但是同时rats也变成了50,同时rats和rodents的地址也相同,而该地址与bunnies的地址不同。由于rodents是rats的别名,因此上述赋值语句与下面的语句等效:rats = bunnies;
也就是说,这意味着将bunnies变量的值赋给rat变量,简而言之,可以通过初始化声明来设置引用,但是不能通过赋值来设置。
2.将引用用作函数参数
引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。按引用传递允许被调用的函数能够访问调用函数中的变量。
交换两个变量的值:交换函数必须能够修改调用程序中变量的值。这意味着按值传递变量将不管用,因为函数将交换原始变量副本的内容,而不是变量本身的内容。但是传递引用时,函数可以使用原始数据,另一种方法是,传递指针来访问原始数据。

#include <iostream>
void swapr(int & a, int & b);   // a, b are aliases for ints
void swapp(int * p, int * q);   // p, q are addresses of ints
void swapv(int a, int b);       // a, b are new variables
int main()
{
    using namespace std;
    int wallet1 = 300;
    int wallet2 = 350;
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << endl;
    cout << "Using references to swap contents:\n";
    swapr(wallet1, wallet2);   // pass variables
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << endl;
    cout << "Using pointers to swap contents again:\n";
    swapp(&wallet1, &wallet2); // pass addresses of variables
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << endl;
    cout << "Trying to use passing by value:\n";
    swapv(wallet1, wallet2);   // pass values of variables
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << endl;
    // cin.get();
    return 0; 
}
void swapr(int & a, int & b)    // use references
{
    int temp;
    temp = a;       // use a, b for values of variables
    a = b;
    b = temp;
}
void swapp(int * p, int * q)    // use pointers
{
    int temp;
    temp = *p;      // use *p, *q for values of variables
    *p = *q;
    *q = temp;
}
void swapv(int a, int b)        // try using values
{
    int temp;
    temp = a;      // use a, b for values of variables
    a = b;
    b = temp; 
}

在这里插入图片描述
void swapr(int & a, int & b); //pass variables
void swapp(int * p, int * q); //pass addresses of variables
void swapv(int a, int b); //pass values of variables
按引用传递swapr和按值传递swapv看起来相同。只能通过原型或函数定义才能知道swapr是按引用传递。然而地址运算符&使得按地址传递swapp。
3.引用的属性个特别之处

#include <iostream>
double cube(double a);
double refcube(double &ra);
int main ()
{
    using namespace std;
    double x = 3.0;
    cout << cube(x);
    cout << " = cube of " << x << endl;
    cout << refcube(x);
    cout << " = cube of " << x << endl;
    // cin.get();
    return 0;
}
double cube(double a)
{
    a *= a * a;
    return a;
}
double refcube(double &ra)
{
    ra *= ra * ra;
    return ra; 
}

在这里插入图片描述
refcube()函数修改了main()中x的值,而cube()没有,这提醒我们为何通常按值传递,变量a位于cube()中,它被初始化x的值,但修改a并不会影响x。但由于refcube()使用了引用参数,因此修改ra实际上就是修改x。
创建临时变量:1.实参的类型是正确,但不是左值;2.实参的类型不正确,但可以转换为正确的类型。
4.将引用用于结构
引用引入主要是为了用于结构和类,而不是基本的内置类型。
使用结构引用参数的方式与使用基本变量引用相同,只需在声明结构参数时使用引用运算符&即可。

#include <iostream>
#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 = {"Ifelsa Branch", 13, 14};
    free_throws two = {"Andor Knott", 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 = {"Throwgoods", 0, 0};
    free_throws dup;
    set_pc(one);
    display(one);
    accumulate(team, one);
    display(team);
// use return value as argument
    display(accumulate(team, two));
    accumulate(accumulate(team, three), four);
    display(team);
// use return value in assignment
    dup = accumulate(team,five);
    std::cout << "Displaying team:\n";
    display(team);
    std::cout << "Displaying dup after assignment:\n";
    display(dup);
    set_pc(four);
// ill-advised assignment
    accumulate(dup,five) = four;
    std::cout << "Displaying dup after ill-advised assignment:\n";
    display(dup);
    // std::cin.get();
    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;
}

在这里插入图片描述
5.将引用用于类对象
将类对象传递给函数时,C++通常的做法是使用引用。可以通过使用引用,让函数将类string、ostream、istream、ofstream和i发stream等类的对象作为参数。

#include <iostream>
#include <string>
using namespace std;
string version1(const string & s1, const string & s2);
const string & version2(string & s1, const string & s2);  // has side effect
const string & version3(string & s1, const string & s2);  // bad design
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;
 // cin.get();
 // cin.get();
    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)   // has side effect
{
    s1 = s2 + s1 + s2;
// safe to return reference passed to function
    return s1; 
}
const string & version3(string & s1, const string & s2)   // bad design
{
    string temp;
    temp = s2 + s1 + s2;
// unsafe to return reference to local variable
    return temp;
}

在这里插入图片描述
6.何时使用引用参数
主要原因:1.能够修改调用函数中的数据类型;2.通过传递引用而不是整个数据对象,可以提高程序的运行速度。

三、默认参数

默认参数指的是当函数调用中省略了实参时自动使用的一个值。如果将void wow(int n)设置成n个有默认值为1,则函数调用wow()相当于wow(1)。这极大地提高了使用函数的灵活性。

#include <iostream>
const int ArSize = 80;
char * left(const char * str, int n = 1);
int main()
{
    using namespace std;
    char sample[ArSize];
    cout << "Enter a string:\n";
    cin.get(sample,ArSize);
    char *ps = left(sample, 4);
    cout << ps << endl;
    delete [] ps;       // free old string
    ps = left(sample);
    cout << ps << endl;
    delete [] ps;       // free new string
    // cin.get();
    // cin.get();
    return 0;
}
// This function returns a pointer to a new string
// consisting of the first n characters in the str string.
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];  // copy characters
    while (i <= n)
        p[i++] = '\0';  // set rest of string to '\0'
    return p; 
}

在这里插入图片描述
该程序使用new创建一个新的字符串,以存储被选择的字符。

四、函数重载

默认参数是能够让使用不同数目的参数调用一个函数,而函数重载能够使用多个同名的函数。“函数重载”指的是可以有多个同名的函数,因此对名称进行重载。
函数重载的关键是函数的参数列表——也称为函数特征标。如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名是无关紧要的。

#include <iostream>
unsigned long left(unsigned long num, unsigned ct);
char * left(const char * str, int n = 1);
int main()
{
     using namespace std;
    char * trip =(char *) "Hawaii!!";   // test value
    unsigned long n = 12345678; // test value
    int i;
    char * temp;
    for (i = 1; i < 10; i++)
    {
        cout << left(n, i) << endl;
        temp = left(trip,i);
        cout << temp << endl;
        delete [] temp; // point to temporary storage
    }
    // cin.get();
    return 0;
}
// This function returns the first ct digits of the number num.
unsigned long left(unsigned long num, unsigned ct)
{
    unsigned digits = 1;
    unsigned long n = num;
    if (ct == 0 || num == 0)
        return 0;       // return 0 if no digits
    while (n /= 10)
        digits++;
    if (digits > ct)
    {
    ct = digits - ct;
    while (ct--)
        num /= 10;
    return num;         // return left ct digits
    }
    else                // if ct >= number of digits
        return num;     // return the whole number
}
// This function returns a pointer to a new string
// consisting of the first n characters in the str string.
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];  // copy characters
    while (i <= n)
        p[i++] = '\0';  // set rest of string to '\0'
    return p; 
}

在这里插入图片描述
只有当函数基本上执行相同的任务,但使用不同形式的数据时,才采用函数重载。

五、函数模块

函数模块是通用的函数描述,也就是使用泛型来定义函数,其中泛型可用具体的类型(int或者double)替换。通过将类型作为参数传递给模板,也可以使用编译器生成该类型的函数。
由于模板允许以泛型(而不是具体类型)的方式来编写程序,因此有时候也被称为通用编程,由于类型是用参数表示的,因此模板特性有时也被称为参数化类型。
建立一个交换模板:

template<typename AnyType>
void Swap(AnyType &a, AnyType &b)
{
   AnyType temp;
   temp = a;
   a = b;
   b = temp;
}

第一行指出,要建立一个模板,并将类型命名为AnyType,关键字template和typename是必需,除非可以使用关键字class和typename。另外,必须使用尖括号。类型名可以任意选择,只要遵循C++的命名规则即可。模板不创建任何函数,而只是告诉编译器如何定义函数。
将模板放在头文件中,并在需要使用模板的文件中包含头文件。
函数模板自动完成重载函数的过程。只需使用泛型和具体算法来定义函数,编译器将为程序中使用的特定参数类型生成正确的函数定义。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值