• C++入门——Day7_函数探幽


    一、C++内联函数

    内联函数是C++为提高程序运行速度所作得一项改进。常规函数和内联函数之间得主要区别不在于编写方式,而是C++编译器如何将它们组合到程序中,要知道它和普通函数得区别,要深入程序内部。

    1:常规函数具体步骤如下:

    1-编译过程的最终产品是可执行程序——由一组机器语言指令组成。运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址,计算机随后将追条执行指令。

    2-有时(比如循环和分支语句)将跳过一些指令,向前或向后跳到特定地址

    3-常规函数调用也使程序跳到另一个地址(函数的地址),并在函数结束时返回

    4-执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行函数代码(有时也把返回值放到寄存器)

    5-跳回到地址被保存的指令处(这就好比阅读文章停下来看脚注,在阅读完成后返回以前阅读的地方类似)。来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定开销

     *就好比我们去吃饭10分钟,但是来回路上就能花掉2h一样,效率很低。

    2:内联函数步骤如下:

    1-内联函数的编译代码与其他程序代码”内联“起来了。编译器将使用相应的函数代码替换函数调用。

    2-对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来

    3-内联函数运行速度比常规函数稍快,但是占用更多的内存空间

    应该有选择性的选择哪种函数

    如果函数执行过程时间 > 调用时间,则更多考虑常规

    如果函数执行过程 < 调用时间,则更多考虑内联

    就是说如果调用耗费时间很长,函数体量比较小,更多考虑内联函数

    3:要使用内联函数,措施如下

    ·函数声明前加上inline;

    ·函数定义前加上inline;

    *通常直接就把所有函数头和函数代码放到原来提供原型的地方

    看下例,通过内联函数squar()(计算参数平方)演示内联函数

    1. #include <iostream>
    2. using namespace std;
    3. inline double square(double x){return x * x;}//简简单单的内联
    4. int main(void)
    5. {
    6. double a,b;
    7. double c = 13.0;
    8. a = square(5.0);
    9. b = square(4.5+7.5);
    10. cout << "a= " << a << ", b= " << b << endl;
    11. cout << "c= " << c << endl;
    12. cout << "Now c = " << square(c++) << endl;
    13. return 0;
    14. }

    结果:

    a= 25, b= 144
    c= 13
    Now c = 169

    内联与宏:

    inline是C++提供的新增特性。C语言使用预处理器语句#define来提供宏——内联代码的原始实现

    比如下面计算平方的宏

    #define SQUARE(x) X*X;

    这并不是通过参数传递实现的,而是通过文本替换实现的——X是”参数“的符号标记

    宏不能按值传递,但是内联函数就是按值传递的

    二、引用变量

    C++新增了一种符合类型——引用变量。引用是已定义的变量的别名。例如,如果将twain作为clement变量的引用,则可以交替使用twain和clement来表示该变量。

    别名的作用是什么呢?

    主要用途是用作函数的形参

    通过将引用变量用作参数,函数将使用原始数据就,而不是其副本

    1:创建引用变量

    比如:

    int rats;

    int & rodents = rats;

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

    这样rodents和rats就可以互换——它们指向相同的值和内存单元!

    如下例:

    1. #include <iostream>
    2. using namespace std;
    3. int main(void)
    4. {
    5. int rats = 10;
    6. int &rodents = rats;
    7. cout << "rats = " << rats << endl;
    8. cout << "rrodents = " << rodents << endl;//10
    9. rodents++;
    10. cout << "rats = " << rats << endl;//11
    11. cout << "rrodents = " << rodents << endl;
    12. cout << "&rats = " << &rats << endl;
    13. cout << "&rodents = " << &rodents << endl;
    14. return 0;
    15. }

    结果如下:

    rats = 10
    rrodents = 10
    rats = 11
    rrodents = 11
    &rats = 0x80d1ff844
    &rodents = 0x80d1ff844

    *引用必须在声明时将其初始化,而不能像指针那样先声明,再赋值

    int rat;

    int &rodent;

    rodent = rat;

    这样是不行!

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

    int &rodnets = rats;

    上面的其实是下述代码的伪装表示:

    int *const pr = &rats;

    看如下代码:
     

    1. #include <iostream>
    2. using namespace std;
    3. int main(void)
    4. {
    5. int rats = 101;
    6. int &rodents = rats;
    7. cout << "rats = " << rats << endl;
    8. cout << "rrodents = " << rodents << endl;//10
    9. cout << "&rats = " << &rats << endl;
    10. cout << "&rodents = " << &rodents << endl;
    11. int bunnies = 50;
    12. rodents = bunnies;
    13. cout << "bunnies = " << bunnies << endl;
    14. cout << "dodents = " << rodents << endl;//50
    15. cout << "rats = " << rats << endl;//50
    16. cout << "&bunnies = " << &bunnies << endl;
    17. return 0;
    18. }

    结果:

    rats = 101
    rrodents = 101
    &rats = 0xa4cdfff644
    &rodents = 0xa4cdfff644
    bunnies = 50
    dodents = 50
    rats = 50
    &bunnies = 0xa4cdfff640

    *

    1)rats和rodents地址和值始终一样,所以它们始终关联

    2)bunnies只是把50赋值给它们了,并没有关联,因为地址不一样

    2:将引用用作函数参数

    通过下例对使用引用和使用指针做个比较,下面演示了三种方法:

    1. #include <iostream>
    2. using namespace std;
    3. void swapr(int &a, int &b);
    4. void swapp(int *pa,int *pb);
    5. void swapv(int a ,int b);
    6. int main(void)
    7. {
    8. int wallet1 = 300;
    9. int wallet2 = 350;
    10. cout << "Wallet1 = " << wallet1 << endl;
    11. cout << "Wallet2 = " << wallet2 << endl;
    12. cout << "Using reference to swap contents:" << endl;
    13. swapr(wallet1,wallet2);
    14. cout << "Wallet1 = " << wallet1 << endl;//350
    15. cout << "Wallet2 = " << wallet2 << endl;//300
    16. cout << "Using pointers to swap contents: " << endl;
    17. swapp(&wallet1,&wallet2);
    18. cout << "Wallet1 = " << wallet1 << endl;//300
    19. cout << "Wallet2 = " << wallet2 << endl;//350
    20. cout << "Tring to use passing by value:" << endl;
    21. swapv(wallet1,wallet2);
    22. cout << "wallet1 = " << wallet1 << endl;//300
    23. cout << "wallet2 = " << wallet2 << endl;//350
    24. return 0;
    25. }
    26. void swapr(int &a, int &b)//按引用传递值,其实此时&a和wallet1关联了,b和wallet2关联
    27. {
    28. int temp;
    29. temp = a;
    30. a = b;
    31. b = temp;
    32. }
    33. void swapp(int *pa,int *pb)//按指针传递值
    34. {
    35. int temp;
    36. temp = *pa;
    37. *pa = *pb;
    38. *pb = temp;
    39. }
    40. void swapv(int a ,int b)//按值传递不可以用了
    41. {
    42. int temp;
    43. temp = a;
    44. a = b;
    45. b = temp;
    46. }

    结果:

    Wallet1 = 300
    Wallet2 = 350
    Using reference to swap contents:
    Wallet1 = 350
    Wallet2 = 300
    Using pointers to swap contents:
    Wallet1 = 300
    Wallet2 = 350
    Tring to use passing by value:
    wallet1 = 300
    wallet2 = 350

    *所以我们看出,引用和指针方法都交换了内容,但是按值传递失败了

    因为在引用传递中,a,b是wallet1和wallet2的别名,而在值传递时,变量ab不过是复制了wallet1和wallet2的新变量,所以交换ab不会改变wallet1和wallet2的值

    3:引用的属性和特别之处

    看如下代码:

    1. #include <iostream>
    2. using namespace std;
    3. double cube(double x);
    4. double recube(double &ra);
    5. int main(void)
    6. {
    7. double x = 3.0;
    8. cout << cube(x) << " = cube of " << x << endl;
    9. cout << recube(x) << " = cube of " << x << endl;//27 = cube of 27,出现了问题,
    10. return 0;
    11. }
    12. double cube(double a)//使用传统方法
    13. {
    14. a *= a * a;
    15. return a;
    16. }
    17. double recube(double &ra)//使用引用方法,并不能改变x的值了
    18. {
    19. ra *= ra * ra;
    20. return ra;
    21. }

    结果:

    27 = cube of 3
    27 = cube of 27

    *这里的ra一直等于x,所以并不会变

    只需要修改成return ra * ra * ra;

    结果就变成27 = cube of 3;

    临时变量、引用参数、const:

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

    这里的5.0是常量,cout << recube2(5.0) << " = cube of " << "5.0" << endl;

    而对于,double recube2(const double &ra),参数是引用变量

    这时候编译器就把5.0传递给一个临时变量,再和引用关联起来

    cout << recube2(5.0+x)也是可以的

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

    因此如果将声明引用指定为const,C++将在必要时生成临时变量

    **引用变量形参应尽可能使用const

    ·使用const可以避免无意中修改数据的编程错误

    ·使用const使得函数能够处理const和非const实参,否则将只能接受非const数据

    ·使用const引用使函数能够正确生成并使用临时变量

    4:将引用用于结构

    引用非常适合于结构和类,如下结构定义:

    1. #include <iostream>
    2. #include <string>
    3. using namespace std;
    4. struct free_throws
    5. {
    6. string name;
    7. int made;
    8. int attempts;
    9. float percent;
    10. };
    11. int main(void)
    12. {
    13. double x = 3.0;
    14. return 0;
    15. }

    则函数

    可以这样编写原型,在函数中将指向该结构的引用作为参数:

    void Set_pc(free_throws & ft);

    如果不希望函数修改传入结构,则应该

    void Set_pc(const free_throws & ft);

    看如下代码:

    1. #include <iostream>
    2. #include <string>
    3. using namespace std;
    4. struct free_throws
    5. {
    6. string name;
    7. int made;
    8. int attempts;
    9. float percent;
    10. };
    11. void set_pc(free_throws &ft);
    12. void display(const free_throws &ft);
    13. free_throws &accumulate(free_throws &target, const free_throws &source);
    14. int main(void)
    15. {
    16. free_throws one = {"Rick",13,14};
    17. free_throws two = {"Jack",10,16};
    18. free_throws three = {"Jerry",7,9};
    19. free_throws four = {"jason",5,9};
    20. free_throws five = {"Michael",6,14};
    21. free_throws team = {"Class 6",0,0};
    22. free_throws dup;
    23. set_pc(one);
    24. display(one);
    25. accumulate(team,one);
    26. display(team);
    27. display(accumulate(team,two));//这里要注意display需要使用accumulate的返回值,所以必须给accumulate设置返回值
    28. display(accumulate(accumulate(team,three),four));//accumulate的返回值是结构体引用,也是他的形参,还缺个four形参,再套上display即可
    29. dup = accumulate(team,five);//新建的dup
    30. cout << "Display team:" << endl;
    31. display (team);
    32. display (dup);
    33. return 0;
    34. }
    35. void set_pc(free_throws &ft)//引用结构体类型
    36. {
    37. if(ft.attempts != 0)
    38. ft.percent = 100 * float(ft.made) / float(ft.attempts);
    39. else
    40. ft.attempts = 0;
    41. }
    42. void display(const free_throws &ft)
    43. {
    44. cout << "Name: " << ft.name << endl;
    45. cout << "Made: " << ft.made << endl;
    46. cout << "Attempts: " << ft.attempts << endl;
    47. cout << "Percent: " << ft.percent << endl << endl;
    48. }
    49. free_throws &accumulate(free_throws &target, const free_throws &source)//改编成有返回值的了
    50. {
    51. target.attempts += source.attempts;
    52. target.made += source.made;
    53. set_pc(target);
    54. return target;
    55. }

    结果如下:

    Name: Rick
    Made: 13
    Attempts: 14
    Percent: 92.8571

    Name: Class 6
    Made: 13
    Attempts: 14
    Percent: 92.8571

    Name: Class 6
    Made: 23
    Attempts: 30
    Percent: 76.6667

    Name: Class 6
    Made: 35
    Attempts: 48
    Percent: 72.9167

    Display team:
    Name: Class 6
    Made: 41
    Attempts: 62
    Percent: 66.129

    Name: Class 6
    Made: 41
    Attempts: 62
    Percent: 66.129

    *

      display(accumulate(team,two));

    //这里要注意display需要使用accumulate的返回值,所以必须给accumulate设置返回值
        display(accumulate(accumulate(team,three),four));

    //accumulate的返回值是结构体引用,也是他的形参,还缺个four形参,再套上display即可

    /*

    2)为什么要返回引用?

    3)返回引用需要注意的问题

    */

    5:将引用用于类对象

    将类对象传递给函数时,C++通常做法是使用引用,看如下代码,使用了string类

    1. #include <iostream>
    2. #include <string>
    3. using namespace std;
    4. string version1(const string &s1,const string &s2);
    5. const string &version2(string &s1, const string &S2);
    6. const string &version3(string &s1, const string &s2);
    7. int main(void)
    8. {
    9. string input;
    10. string result;
    11. string copy;
    12. cout << "Enter a string: ";
    13. getline(cin,input);
    14. copy = input;
    15. cout << "Your string as entered: " << input << endl;
    16. result = version1(input,"***");//把输入加上前缀后缀
    17. cout << "Your string enhanced: " << result << endl;
    18. cout << "Your original string: " << input << endl;
    19. cout << "---------------------------------" << endl;
    20. result = version2(input,"###");
    21. cout << "Your string enhanced: " << result << endl;//rusult是version返回的结果,就是s1,s1和input关联
    22. cout << "Your original string: " << input << endl;//这个input和s1引用,应该一样
    23. cout << "---------------------------------" << endl;
    24. input = copy;
    25. result = version3(input,"@@@");
    26. cout << "Your string enhanced: " << result << endl;
    27. cout << "Your original string: " << input << endl;
    28. return 0;
    29. }
    30. string version1(const string &s1,const string &s2)//有const即使不匹配也会创建变量
    31. {
    32. string temp;
    33. temp = s2 + s1 + s2;
    34. return temp;
    35. }
    36. const string &version2(string &s1, const string &s2)//返回string类引用
    37. {
    38. s1 = s2 + s1 + s2;//着跟第一个的temp还是不一样的,temp是临时变量,最后就消失了
    39. return s1;
    40. }
    41. const string &version3(string &s1, const string &s2)
    42. {
    43. string temp;//temp用完了就没了,引用没法返回了
    44. temp = s2 + s1 + s2;
    45. return temp;
    46. }

    结果:

    Enter a string: aaa
    Your string as entered: aaa
    Your string enhanced: ***aaa***
    Your original string: aaa
    ---------------------------------
    Your string enhanced: ###aaa###
    Your original string: ###aaa###
    ---------------------------------

    version3直接崩溃退出了

    *version3中的temp用完了就没了,引用没法返回了

    6:对象、继承和引用

    看ostream和ofstream类,它们有一个有趣的属性,ofstream对象可以使用ostream类的方法,这就使得文件输入/输出格式和控制台输入/输出相同,这种使得能够将特性从一个类传递给另一个类的语言特性被称为继承

    继承的另一个特征是,基类引用可以指向派生类对象,而不许强制类型转换

    就是说,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数

    看下例:我们使用基类和派生类对象

    1. #include <iostream>
    2. #include <fstream>
    3. #include <cstdlib>
    4. using namespace std;
    5. const int LIMIT = 5;
    6. void file_it(ostream &os,double fo,const double fe[],int n);
    7. int main(void)
    8. {
    9. ofstream fout;//用子类fstream创建的对象
    10. const char *fn = "ep-data.txt";
    11. fout.open(fn);
    12. if(!fout.is_open())//打开失败
    13. {
    14. cout << "Can't open " << fn << ". Bye!" << endl;
    15. exit(EXIT_FAILURE);
    16. }
    17. double objective;
    18. cout << "Enter the focal Length of your telescope objective in mm: ";
    19. cin >> objective;
    20. double eps[LIMIT];
    21. for (int i = 0; i < LIMIT; i++)
    22. {
    23. cout << "EyePices" << i+1 << ": ";
    24. cin >> eps[i];
    25. }
    26. file_it(cout,objective,eps,LIMIT);//cout是父类iostream中的方法
    27. file_it(fout,objective,eps,LIMIT);//fout是子类创建的对象
    28. cout << "Done!" << endl;
    29. return 0;
    30. }
    31. void file_it(ostream &os,double fo,const double fe[],int n)//基类的引用可以指向基类对象和派生类对象
    32. {
    33. os << "Focal length of objective: " << fo << endl;
    34. os << "f.1. eyepieces" << " magnification" << endl;
    35. for(int i = 0; i < n;i++)
    36. {
    37. os << " " << fe[i] << " " << int(fo/fe[i] + 0.5) << endl;
    38. }
    39. }

    控制台结果:

    Enter the focal Length of your telescope objective in mm: 1800
    EyePices1: 30
    EyePices2: 19
    EyePices3: 21
    EyePices4: 7.6
    EyePices5: 12
    Focal length of objective: 1800
    f.1. eyepieces magnification
         30 60
         19 95
         21 86
         7.6 237
         12 150
    Done!

    文本结果:

     

    *

    void file_it(ostream &os,double fo,const double fe[],int n)

    函数的第一个参数比较难理解,它是采用了基类,就是ostream的引用对象,这样我们函数调用的时候既可以使用cout对象,也能使用fout的子类对象

    7:何时使用引用参数

    使用引用参数的原因有两个:

    ·程序员能够修改调用函数中的数据对象

    ·通过传递引用而不是整个数据对象,可以提高程序运行速度

    当数据对象比较大的时候(比如结构和类的对象),引用和指针就比较重要了,因为参数引用实际上是基于指针的代码的另一个结构

    那么何时使用指针,什么时候用引用,什么时候使用值传递呢?

    1)对于传递的值而不做修改的函数

    ·如果数据量比较小,如内置数据结构或小型结构,则按值传递

    ·如果数据对象是数组,则使用指针,因为这是唯一选择,并将指针声明为指向const的指针

    ·如果数据对象是较大的结构,则使用const指针或者引用,以提高程序的效率,这样可以节省复制结构所需的时间和空间

    ·如果数据对象是类对象,则使用const引用

    2)对于修改调用函数中数据的函数

    ·如果数据对象是内置数据结构,则使用指针。如果看到比如fixit(&x)这样的代码,则修改X

    ·如果数据对象是数组,则只能使用指针

    ·如果数据对象是结构,则使用引用或指针

    ·如果对象结构是类,则使用引用

    三、默认参数

    程序如下:

    1. #include <iostream>
    2. using namespace std;
    3. const int ArSize = 80;
    4. char *left(const char *str,int n = 1);//在这里设置好默认参数
    5. int main(void)
    6. {
    7. char sample[ArSize];
    8. cout << "Enter a string: " << endl;
    9. cin.get(sample,ArSize);
    10. char *ps = left(sample,4);//该函数提出4个字符
    11. cout << ps << endl;
    12. delete []ps;
    13. ps = left(sample);//不设置第二个参数,使用默认参数!
    14. cout << ps << endl;
    15. return 0;
    16. }
    17. char *left(const char *str,int n)
    18. {
    19. if(n < 0)
    20. n=0;
    21. char *p = new char[n+1];//开辟了n+1的空间
    22. int i;
    23. for(i = 0; i < n && str[i]; i++)//具体能使用i个,且str[i]有效,就是str没越界
    24. p[i] = str[i];
    25. while(i <= n)//剩余的设置为空字符
    26. p[i++] = '\0';
    27. return p;
    28. }

    结果:

    Enter a string:
    hello
    hell
    h

    四、函数重载

    *左值,右值

    1. int a = 10; //a就是左值,10就是右值
    2. int b =20;//b是左值,20是右值
    3. int c = a + b;//此时a+b是右值
    4. a + b = c;//这样就不行

    对于能够对它进行取地址的,是左值,不能取地址的是右值

    比如在右边的10,20,他们是存放在寄存器的,你不能对它进行取地址

    *左值引用,右值引用

    1. int a = 10; //a就是左值,10就是右值
    2. int b =20;//b是左值,20是右值
    3. int &c = a;//a是左值,所以这个引用时左值引用
    4. int &d = 10;//这样就不行
    5. int &d = (a+b);//这样也不行
    6. const int &d = 10;
    7. const int &c = (a+b);//加上const就可以实现右值引用,其实是和临时变量产生关系

    常引用中不能通过d,c修改值

    因此为了修改数据,引入了右值引用&&

    int &&x = 10;//这就是右值引用

    int &&y = (a+b);

    函数多态是C++在C基础上新增的功能。默认参数能让我们使用不同数目的参数调用同一个函数

    而函数多态(重载)能让我们使用多个同名的函数。通常我们采用重载这种叫法。

    比如说piggy小姐可以在棒球场为家乡球队助威(root),也可以在地里种植(root)植物,根据上下文来确定不同情况下,root意义是什么。C++同样通过上下文确定重载函数版本

    *函数重载的关键是函数的参数列表——也称函数特征标。如果两个函数的参数数目、类型相同,参数的列表顺序也相同,则它们的特征标相同,而变量名无关紧要。

    C++允许我们定义名称相同的函数,条件是特征标不同。

    比如:
     

    1. void print (const char * str, int width);
    2. void print(double d,int width);
    3. void print(long ;,int width);
    4. void print(int i,int width);
    5. void print(const char *str);

    编译器会根据所采用的用法来对应特征标的原型

  • 相关阅读:
    Docker 基本管理
    小白学编程(CSS):跳动的文字
    诡异,明明更新成功了状态,查不出来了
    Nanoprobes纳米探针丨Nanogold偶联物的特点和应用
    跨语言指令调优深度探索
    【直播笔记0629】 并发编程二:锁
    IDEA远程连接服务器Java程序进行断点调试(对Docker中Tomcat容器,jar,Tomcat的远程调试看这篇就够了)
    linux sh脚本各种数值进制转换(比如10进制转16)若干例子
    【开源讲解】
    Java中的23种设计模式
  • 原文地址:https://blog.csdn.net/leikang111/article/details/125320954