• C++ 入门基础(收尾) 内联函数



    1 内联函数

    1.1 概念

    对于如Swap(交换)这样频繁被调用的短小函数,编译器为其去创建栈帧是很麻烦的,因此C++中新增了内联函数。
    以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。

    在C语言中解决这个问题的方式是去定义宏:

    //频繁调用短小函数
    int Add(int x, int y)
    {
    	int z = x + y;
    	return z;
    }
    
    //c语言实现方法:实现ADD的宏
    #define ADD(x,y) ((x)+(y))
    
    int main() 
    {
    	Add(1, 2);
    	Add(1, 2);
    	Add(1, 2);
    	Add(1, 2);
    	ADD(1, 2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    为什么C++要出内敛?
    因为在C语言定义宏的过程中可能会出现各种形式的错误写法,如:

    #define ADD(int x, int y) return x + y;
    #define ADD(x, y) return x + y;
    #define ADD(x, y) (x + y);
    #define ADD(x, y) x + y
    
    • 1
    • 2
    • 3
    • 4

    因此C++拓展了内联函数的写法,实现的效果和宏替换是一样的,解决了宏定义的晦涩难懂、不支持调试、易错的问题,只要在定义的函数前加一个inline,写法如下:

    inline int Add(int x, int y)
    {
    	int z = x = y;
    	return z;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.2 特性

    1. inline是一种以空间换时间的做法,省去调用函数建立栈帧的开销。所以代码很长(一般是十行)或者有循环/递归的函数不适宜使用作为内联函数。
    2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
      比如有一个10行的函数,有1000个调用的地方,通过inline函数替换和不替换的空间差别是很大的:
    替换:1000*101000次替换,每次替换的行数为十行) 
    不替换:1000+101000是调用函数的代码,10是定义函数的10行代码)
    
    • 1
    • 2
    1. inline不建议声明和定义分离,分离会导致链接错误。因为inline在预编译时被展开,就没有函数地址了,链接就会找不到。

    2 auto关键字

    2.1 auto简介

    自C++11后,auto关键字用于声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。

    int TestAuto()
    {
    	return 10;
    }
    
    int main() 
    {
    	int a = 10;
    	auto b = a; //整形 -> int
    	auto c = &a;//地址 -> int*
    	auto d = 'a';//字符 -> char
    	auto e = TestAuto();//整形 -> int
    
    	//输出由auto修饰的变量b,c,d,e的类型
    	cout << typeid(b).name() << endl;
    	cout << typeid(c).name() << endl;
    	cout << typeid(d).name() << endl;
    	cout << typeid(e).name() << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    输出结果如下:
    在这里插入图片描述

    注意】:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

    2.2 auto细则

    1. auto与指针和结合起来使用

    用auto声明指针类型时,用autoauto*没有任何区别,但用auto声明引用类型时则必须加&

    int main()
    {
    	int x = 10;
    	auto a = &x;
    	auto* b = &x;//声明指针类型
    	auto& c = x;//声明引用类型
    
    	cout << typeid(a).name() << endl;
    	cout << typeid(b).name() << endl;
    	cout << typeid(c).name() << endl;
    	
    	//通过三种方式修改x的值
    	*a = 20;
    	cout << "x:" << x << endl;
    	*b = 30;
    	cout << "x:" << x << endl;
    	c = 40;
    	cout << "x:" << x << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    结果如下:
    在这里插入图片描述

    2. 在同一行定义多个变量

    当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

    void TestAuto()
    {
    	auto a = 1, b = 2;
    	auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    为了避免滥用auto,以下两种写法被编译器禁止:

    3.auto不能作为函数的参数和返回值

    //以下是错误写法
    //1.auto作函数参数
    void TestAuto(auto a)
    {}
    
    //2.auto作函数返回值
    auto TestAuto()
    {
    	return 10;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4. auto不能直接用来声明数组

    void TestAuto()
    {
    	int a[] = {1,2,3};
    	auto b[] = {456};//不能这样写
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.3 使用场景(范围for简介)

    要想对一个数组进行遍历,我们之前通常会用下面两种写法:

    void TestFor()
    {
    	int array[] = { 1, 2, 3, 4, 5 };
    	//对元素个数
    	for (int i = 0; i < sizeof(array) / sizeof(int); ++i)
    		array[i] *= 2;
    	//对指针偏移
    	for (int* p = array; p < array + sizeof(array) / sizeof(int); ++p)
    		cout << *p << " ";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    对于一个有范围的集合而言,对程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“:”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

    如果你想遍历输出一个数组,你可以这样写:

    void TestFor()
    {
    	int array[] = { 1, 2, 3, 4, 5 };
    	for (auto e : array)
    		cout << e << " ";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果你想像上面的代码一样,通过遍历将数组里每个元素的值乘2,再输出的话,同样也可以写出相应的代码,下面我将再写一个代码,来说明范围for的特性:

    void TestFor()
    {
    	int array[] = { 1, 2, 3, 4, 5 };
    	for (auto e : array)
    		e *= 2;
    	for (auto e : array)
    		cout << e << " ";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    它的输出结果并不是预想的使所有值乘2:
    在这里插入图片描述

    原因是上面范围for的操作实际上是依次取array中的数据,赋值给e,自动判断结束。由于e只是array的临时拷贝,因此并不会因为e*=2而改变array中元素的值。

    正确的写法应该如下:

    void TestFor()
    {
    	int array[] = { 1, 2, 3, 4, 5 };
    	//auto后面应该加&,从而改变array的值
    	for (auto& e : array)
    		e *= 2;
    	for (auto e : array)
    		cout << e << " ";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    for循环迭代的范围必须是确定的
    对于数组而言,就是数组中第一个元素和最后一个元素的范围

    注意:以下代码就有问题,因为for的范围不确定

    void TestFor(int array[])
    {
    	//由于这里的array是传参所得,因此array默认是指针,也就没有第一个元素和最后一个元素的范围
    	for(auto& e : array)
    		cout<< e <<endl; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3 关键字nullptr(C++11)

    NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

    #ifndef NULL
    #ifdef __cplusplus
    #define NULL 0
    #else
    #define NULL ((void *)0)
    #endif
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    也就是说,在C++中,NULL实际上和0一个东西,看下面这段代码:

    void f(int)
    {
    	cout<<"f(int)"<<endl; 
    }
    void f(int*) 
    {
    	cout<<"f(int*)"<<endl; 
    }
    int main()
    { 
    	f(0);
    	f(NULL);
    	f((int*)NULL);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
    程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
    为了避免像上面这段代码出现的本意上的错误,使用空指针时,尽量用nullptr来代替NULL。

  • 相关阅读:
    35. 干货系列从零用Rust编写负载均衡及代理,代理服务器的源码升级改造
    英国生活需要交纳哪些税?
    如何实现一个数据库的 UDF?图数据库 NebulaGraph UDF 功能背后的设计与思考
    Windows Server 2016使用MBR2GPT.EXE教程!
    python读取文件由于编码问题失败汇总
    c++(五)
    MyBatus-Plus保姆级快速上手教程
    服务器之间传递数据脚本
    基于知识问答的上下文学习中的代码风格11.20
    基于SSM 离退休管理平台-计算机毕设 附源码 52629
  • 原文地址:https://blog.csdn.net/m0_63303316/article/details/126746483