c++入门 内联函数 auto 指针空值nullptr

在这里插入图片描述

1. 内联函数

1.1. 宏的介绍

在介绍内联函数之前,我们先说一下为什么c++中会有这个东西。


  • 在c语言中,有宏这个概念
#define ADD(x,y) ((x)+(y))

int Add(int x, int y)
{
	return x+y;	
}

宏函数相较于一般函数,有以下优点:

  1. 没有严格类型的检查
  2. 提高效率,针对频繁调用的小函数,不需要建立函数栈帧(主要)

但是宏对于使用者的体验来说就不是很好
缺点:

  1. 容易出错,语法点多
    写函数不容易出错,但是写宏很容易出错
#define ADD(int x,int y) {return x+y;}
#define ADD(x,y) (return x+y;)
#define ADD(x,y) return x+y;
#define ADD(x,y) x+y;
#define ADD(x,y) x+y;
#define ADD(x+y) (x+y)

上面的写法都是有问题的,最后一个看起来好像正确 ,但是对 x 和 y 没有括号的约束,很容易出现运算顺序的错误。
2. 不能调试
宏会在预编译阶段在函数中进行替换,而 vs 在调试的时候,不会展示预编译的操作。
3. 没有安全类型的检查

int main()
{
		int a = 1, b = 2;
		ADD( a|b , a&b );
		return 0;
}

这里的位运算传入宏函数中,如果是(x+y),x和y都没有括号的限制,最后运算就会改变,“+”的运算的等级高于 “|” 和 “&”。

1.2. 内联函数的使用

因为宏函数的缺点,祖师爷在创建c++的时候引入了内联函数的概念。
关键字: inine
在定义函数前,加上上面的关键字就可以了。

#include<iostream>
inline int add(int x,int y)
{
	return x+y;
}

int main()
{
	int ret=add(1,2);
	return 0;
}

使用方式很简单,就是加上 inline 这个关键字。
在这里插入图片描述
一般函数,在程序预编译的链接环节,会用用函数的地址调用函数
在运行的过程中,打开反汇编会有这句 “call”,就说明了程序通过调用函数来使用函数(debug版本下函数会默认调用,而不是在函数内展开)
在这里插入图片描述
但是在我们使用 release 版本,通过反汇编
在这里插入图片描述

属性---优化---内联函数拓展---只适用于 ——inline(/ob1)
属性---常规---调试信息格式---程序数据库(/Zi)

如果还是没有显示出来可以通过上面的步骤设置。

在这里,我们可以看见,虽然我们调用了这个函数,但是并没有通过地址寻找这个函数,而是直接在主函数内部展开进行操作。

  • 这样的方法继承了宏 的优点 不需要建立函数栈帧的优点,能提高程序的效率。
  • 而且写出来是函数的样子,大大降低了出错率。

1.3. 内联函数注意事项

使用内联函数,不是说什么时候都可以使用,还需要注意以下几点:

1. 内联函数是由编译器决定的

#include<iostream>
inline namespcae std;

inline void f()
{
	int a=10;
	a=20;
	aa=30;
	a=40;
	a=50;
	a=60;
	a=70;
	cout<<a<<endl;
}
int main()
{
	f();
	return 0;
}

在运行这段代码的时候,我们查看反汇编
在这里插入图片描述

此时,会发现虽然我们定义的是内联函数,但是程序还是去找了函数的地址。

这是因为虽然定义的时候设置的是内联函数,但是它能不能当内联函数还是要看编译器,而不是你。
就像是你递交了一份简历,决定你能不能进公司的不是你有没有交简历这件事,而是看公司答不答应。

为什么编译器会这样搞呢?
我们可以分析一下代码数量:
我定义了一个内联函数 Func() 100行
在程序中 调用了 100次这个函数

  • 如果编译器没有自动识别的操作
    最后程序会多出: 10000行代码。
    内联函数会在调用的地方展开,展开后会加入这么多的代码,会造成代码膨胀。
    所以程序一般会默认通过内容只有几行(2,3行)的代码作为内联函数
  • 如果程序有这个功能
    最后对多出: 200行
    100行是定义这个函数的行数,另外的100行是程序在运行的过程中,会自动生成调用函数的语句,也就是每调用一次生成一句。

2. 内联函数的声明和定义不能分离在两个文件中

Func.h

#pragma once

#include<iostream>
using std::cout;
using std::endl;
inline void f(int i);

Func.cpp

#include"Func.h"

inline void f(int i)
{
        cout << i << endl;
}

test.cpp

#include"Func.h"
int main()
{
        int ret = add(2, 3);
        printf("%d   ", ret);

        f(1);
        return 0;
}

在上面的三个文件中,我们分别将内联函数 f 的声明和定义分别放入两个文件中。此时我们检查错误
在这里插入图片描述
LNK2019 是预编译过程中链接过程的错误

为什么会出现这样的错误?
首先,我们知道,c++在预编译过程中,会在test.cpp中展开头文件,但是头文件中只有函数的声明,test.cpp中只有函数的调用,但是能通过编译汇编环节。
而test.cpp中没有定义,就回去其他文件中找,也就是在链接过程中区寻找函数,但是是通过函数修饰名去寻找地址,而 内联函数在符号表里没有地址,也就是找不到调用的函数,所以在链接的时候无法找到内联函数。

但是如果我们在 Func.cpp 中调用函数,就会通过

因为在链接过程中,头文件展开有函数的声明,Func.cpp有函数的定义和调用,也就是说在 Func.cpp中会直接找到函数,可以运行。

所以为了避免出现上面的错误,我们可以在定义内联函数的时候,将内联函数的声明和定义都写在头文件中。
Func.h

#include<iostream>
using std::cout;
using std::endl;

inline void f(int i)
{
        cout << i << endl;
}
void fx();

test.cpp

#include"Func.h"
int main()
{
        int ret = add(2, 3);
        printf("%d   ", ret);

        fx();
        f(1);
        return 0;
}

在这里插入图片描述
这样的话,不管是哪个文件使用该头文件,展开后都会有函数的声明,定义,调用,就不会 出现上面的情况。

一般我们用

  • inline 内联函数来代替宏函数
  • const enum 枚举来替换宏常量

2. auto关键字

2.1. auto的使用介绍

作用:根据右边的值,自动推导左边值的类型

int main()
{
		int a = 0;
		auto b = 1 ;
		auto c = &a;
		auto& d = a;
		
 		return 0;
 }

普通场景下没有什么价值
对于类型很长,就有价值,可以起到简化代码的作用

  • auto 不能作为函数的参数
  • auto不能直接用来声明函数
typdeif().name()

显示类型

#include<iostream>
using namespace std;
int main()
{
		int a = 10;
		auto c = &a;
		auto& b = a;
		cout<<typdeif(c).name()<<endl;
		cout<<typdeif(b).name()<<endl;
		
		return 0;
}

2.2. 范围for的使用

作用:自动输出数组中的内容

#include<iostream>
using namespace std;
int main()
{
		int array[]={ 2, 4, 6, 8, 10};
		for(auto e:array)
		{
			cout<<e<<"  ";
		}
		cout<<endl;
		for(auto e: array)
		{
			e*=2;
		}
		return 0;
}

在这里插入图片描述
注意:

  1. 会一次一次取原数组中的数据,赋值给e
  2. 会自动判断结束,自动迭代
  3. 这里的 e 只是数组中数据的拷贝,改变 e 的值原数组中的值不会改变
  4. 如果想要改变数组中的值,需要引用
  5. 这里加上 auto 就不需要判断数据类型,如果里面是 int e,数组是double 类型还需要换类型,麻烦。
		for(auto& e: array)
		{
			e*2=2;
		}

同时注意

  1. 语法固定范围for每次只会取数组中的值,也就是直接把数字的值赋给e,直接使用指针接受非指针的值,所以这里不允许使用指针
    在这里插入图片描述
  2. 数组名传入函数后,不会代表整个数组,运行会报错
    在这里插入图片描述

3. 指针空值

c++中对 NULL 定义,不是 c 语言中的指针类型,而是直接定义为 0
c++中 NULL 的定义

#define NULL 0;

c++中的 NULL 是作为宏定义的
我们可以测试一下:

#include<iostream>
using namespace std;
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;
}

NULL 一般作为指针空值,但是当我们将 NULL 作为参数传入,它会进入参数 int 为类型的函数,也就是说c++中默认 NULL 是0;
如果我们队 NULL 强制类型转换后,NULL 才会进入参数为 int* 类型的函数。
在这里插入图片描述
在 c++ 中,后序的更新,c++不敢将 NULL 的宏定义修改,有些人的程序可能将 NULL 作为空值,修改后会导致一些人的程序出现问题,所以就引入了 nullptr 在c++中作为指针空值

所以在c++中,我们使用 nullptr 来表示空指针
nullptr 类型(void*)
使用时,需注意:

  1. 使用nullptr 时,不需要包含头文件,nullptr 是c++11 作为新关键字引入的。
  2. 在 c++11 中,sizeof(nullprt)与size((void*)0) 所占的字节数相同。
  3. 为了提高代码以后不出问题,最好在使用指针空值的时候使用 nullptr。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值