首页 > 编程语言 >[C++从入门到精通] 2.inline内联函数、const的相关用法

[C++从入门到精通] 2.inline内联函数、const的相关用法

时间:2023-12-22 21:05:24浏览次数:32  
标签:const 函数 int C++ char func 内联 inline

  • 作者: 丶布布


文章预览:

  • 一、返回类型
  • 二、内联函数inline
  • 三、函数杂合用法总结
  • 四、const char*、char const*、char* const三者的区别
  • 五、函数形参中带const



一、返回类型

前置类型: 在函数声明和定义的时候,把函数返回类型写到函数名字之前的形式,叫前置返回类型

void func(int a); //函数声明
void func(int a)  //函数定义
{
	return;
}

后置类型: C++11中,在函数声明和定义的时候,把返回类型写在参数列表之后的形式,叫后置返回类型

auto  func1(int a) -> void; //函数声明
auto  func1(int a) -> void  //函数定义
{
	return;
}

作用差异: 随着对类这种概念学习的不断深入,有一些成员函数返回类型非常复杂,这时使用后置返回类型会使得代码看起来比较清爽,容易看懂。

PS:建议函数声明放在.h文件,函数定义放在.cpp文件中,如果函数定义放在头文件A.h中,当B.cppC.cpp都包含A.h的时候,会报错:找到一个或多个多重定义符号(函数重复定义)。


二、内联函数inline

定义: 在函数定义前,增加关键字inline,会使该函数变成内联函数。

适用: 当函数体很小,并且被频繁调用的函数,适合成为inline函数被调用。

解析: 大家都知道,调用函数是需要消耗系统资源的,尤其是函数体很小,只有一两行的代码,但是我需要一个循环频繁调用,这种频繁调用一个函数体很小的小函数是很不划算的,因为调用一个函数需要将函数形参压栈(开辟内存),往堆栈里面放数据,函数调用完之后返回出栈,这种调用过程需要频繁消耗内存,可能函数代码执行只用了0.1s,但是压栈出栈就用了1s,所以压栈出栈这种内存操作很不划算。

用法:

//.h
inline int func2(int test)
{
	return 1;
}

特性:

1、在编译阶段,当编译器识别到函数为inline函数时,系统会尝试将调用inline函数的动作替换为函数本体,以此来提升性能。

int main()
{
   int abc = func2(5); //当编译器识别到func2为内联函数inline时
   int abc = 1;        //系统会尝试用int abc = 1(函数本地);来取代int abc = myfunc(5)(调用内联函数的动作);前者毫无疑问比压栈出栈这种正常的函数调用快很多
}

2、inline函数只是我们开发者对编译器的一个建议,编译器可以尝试去做,也可以不去做,这取决于编译器的诊断功能,就是说,决定权在编译器,我们控制不了。

3、上面我们说过函数声明要放到.h文件中,函数定义放在.cpp文件中。而内联函数的恰恰相反,内联函数的定义就要放到.h文件中,这样需要用到内联函数的.cpp文件时,能够通过#include直接把内联函数的源代码#include进来。以便编译器找到函数本体的源代码,并尝试使用函数体内的实现语句替换函数的调用,达到更高效的目的。

缺点以及规避:

1、代码膨胀问题:内联函数的实现语句较多

如:

//.h 内联函数func2定义 
inline int func2(int test)
{
    int num;
    num= test;
	return num;
}

而我们调用内联函数的语句只有一句:

int main()
{   
    int temp = func2(5);
    return 0;
}

即上面这种情况我们使用内联函数时,需要用内联函数的函数体(3句)替换一句调用函数(1句),很明显不太划算,造成了代码膨胀。

在举个夸张一些的例子,假设某函数的函数体很多,执行需要1000s,那么将其转换成内联函数其实也就节省个压栈出栈的时间,比如1s,那么整体来说使用内联函数与否已经意义不大了,所以我们一般要求内联函数的函数体要尽量小一些。

注意:各种编译器对inline函数的处理各不相同,有的可能比较智能,有的编译器看你内联函数有计算什么的比较复杂,可能会直接当成普通函数处理。所以要求inline函数尽量简单,代码尽可能少,尤其是涉及到循环、分支、递归调用等语句、尽量不要出现在inline函数中,否则编译器很可能会因为你写这些代码而拒绝让这个函数成为一个inline函数。

拓展:
constexper函数,也要求函数体比较简单,否则会报错,我们可以看成是更严格的一种内联函数。
#define宏展开也类似于内联函数,但也有一些,比如类型检查方面的差别,这里不再展开说明,感兴趣的自行查阅。


三、函数杂合用法总结

1、函数返回类型为void,表示无返回值类型。但我们可以调用一个返回类型是void的函数,让它作为另一个另一个返回类型是void函数的返回值。

void func_a()
{
    return;
}

void func_b()
{
    //return;       //可以
    return func_a();//这也可以
}

2、函数返回指针和返回引用的情况

情景1: 函数返回指针类型

int *func_c()
{
    int tempValue = 9;
    return &tempValue;  //这不可以,因为函数执行完毕后,tempValue的内存被系统回收,不能在继续使用,返回出去毫无意义。
}
int main()
{
    int *p = func_c();
    *p = 6;            //你往一个不属于你的地址写了数字。    
    return 0;
}

大家想一想上面的指针函数调用会出现什么问题?

上面的代码虽然可以正常编译通过,这时调用指针函数func_c()时,系统会为函数内部的临时变量tempValue分配一段内存地址,但是当调用结束时,临时变量tempValue的地址会被系统回收,这时int *p = func_c();中指针p指向的是一段你不能操控的地址。*p = 6;虽然将6写进了指针p指向的地址中,但是这段地址是不属于你的,造成的后果就是轻则运行一段时间啪—崩溃,重则运行时立马啪—崩溃。。

解决:将局部变量tempValue改成全局变量,这样其不会因为函数执行结束而被系统回收内存,至始至终都会保留其内存地址。

情景2: 函数返回引用类型

int &func_d()
{
    int tempValue = 9;
    std::cout << "tempValue内存地址:" << &tempValue << std::endl;
    return tempValue;  
}
int main()
{
    int &k = func_d();
    std::cout << "k内存地址:" << &k << std::endl;
    k = 7;         //你往一个不属于你的地址写了数字。        
    return 0;
}

和情景1类似,都是往一个不属于你的地址写了数字

[C++从入门到精通] 2.inline内联函数、const的相关用法_const相关用法


可以看到,虽然k的地址和函数func_d返回的引用地址相同,但是这块地址在传给k之前已经被系统回收了,是不属于你的,所以不可以用。

还有其他调用方法:

int main()
{
    int k = func_d();     //返回值承接一个整型值
    std::cout << "k内存地址:" << &k << std::endl;
    k = 7;                //安全
    return 0;
}

相信这里大家已经懵逼了,明明函数func_d返回的是一个引用,为什么却能用一个整形值k来接?

不过确实有这种调用,大家知道就好了,我感觉应该是编译器内部做了相关的转换,将函数返回的引用内部转换成了整形值,我们可以看到k的地址已经不是函数func_d调用结束后被释放掉的的局部变量tempValue的地址了,而是被系统新分配了一块内存地址,所以可以对其的地址处进行存值。

[C++从入门到精通] 2.inline内联函数、const的相关用法_inline内联函数_02

3、没有形参可以保持形参列表为空,或者形参为viod类型

int func_e(void)
{
    return 1;
}

4、如果一个函数我们不调用的话,则该函数可以只有声明部分,没有定义部分。

5、普通函数定义只能定义一次(定义放在.cpp文件中),声明可以声明多次(每次include一个头文件,都相当于声明了一次该头文件的函数)

6、C++中,更习惯用引用类型的形参来取代指针类型的形参,提倡在C++中,多使用引用类型的形参。

7、C++中,函数允许同名,但形参列表的参数类型或者数量应该有明显的区别。

void func_f(int a)      
{
    return;
}

void func_f(double b)      //可以,名称相同,形参类型不同
{
    return;
}

void func_f(int a, int b)  //可以,名称相同,形参数量不同
{
    return;
}

四、const char*、char const*、char* const三者的区别

标签:const,函数,int,C++,char,func,内联,inline
From: https://blog.51cto.com/u_16436086/8938524

相关文章

  • [C++从入门到精通] 3.string类型的相关用法
    作者:丶布布文章预览:一.C++基本变量类型二.String对象的定义和初始化三.String对象的操作一.C++基本变量类型基本类型:int、double等。复合类型:引用、指针、const等。标准库类型:string(可变长字符串的处理)、vector(一种集合或容器的概念)、迭代器。对于字符串,在现实生活中,string......
  • [C++] 获取工程路径、解决方案路径和.exe路径
    作者:丶布布文章预览:......
  • [C++] 互斥锁(unique_lock、lock_guard)
    作者:丶布布......
  • [C++] 强制类型转换(dynamic_cast和dynamic_Pointer_cast)
    作者:丶布布1、指引或者引用的向上转换,向下转换例如基类Father,Son继承Father,派生类Son.。Father—>Son则为向下转换,Son—>Father则为向上转换。向上转换为隐士转换,向下转换需要dynamic_cast或者c的转换方式。向上转换:structFather{//基类Father};structSon:Father{//......
  • [C++ 从入门到精通] 5.迭代器精彩演绎、失效分析及弥补、实战
    作者:丶布布文章预览:一.迭代器简介二.容器的迭代器类型三.迭代器begin()/end()操作,反向迭代器rbegin()/rend()操作四.迭代器相关操作五.constiterator迭代器5.1.cbegin()和cend()操作六.迭代器失效及弥补6.1.灾难程序演示七.范例演示7.1用迭代器遍历string数据7.2vect......
  • [C++从入门到精通] 10.回顾类内初始化、默认构造函数、=default
    ......
  • [C++ 从入门到精通] 6.static_cast、dynamic_cast等显示类型转换
    作者:丶布布文章预览:一.隐式类型转换二.显式类型转换(强制类型转换)static_cast显示转换dynamic_cast显示转换const_cast显示转换reinterpret_cast显示转换三.总结一.隐式类型转换含义:隐式类型转换:系统自动进行,不需要开发人员介入。intm=3+45.6;//48因为返回的int型......
  • [C++ 从入门到精通] 带你彻了解C++中String类型的用法
    作者:丶布布文章预览:一.C++基本变量类型二.String对象的定义和初始化三.String对象的操作一.C++基本变量类型基本类型:int、double等。复合类型:引用、指针、const等。标准库类型:string(可变长字符串的处理)、vector(一种集合或容器的概念)、迭代器。对于字符串,在现实生活中,string......
  • C++ Qt开发:Charts绘图组件概述
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍QCharts二维绘图组件的常用方法及灵活运用。QtCharts提供了一个强大且易于使用的工具集,用于在......
  • C++中的 mutable 关键字
    Parcel类中mDataPos被修饰为mutable类型变量。C++中的mutable是一个关键字,用于修饰类的成员变量。mutable关键字的作用是允许被修饰的成员变量在const修饰的成员函数中被修改,即使这些函数被声明为const。下面是mutable关键字的使用示例:#include<iostream>usin......