详解c++之细碎知识


img

1.auto关键字

1.1auto是什么 :

auto 是一个类型说明符,它能让编译器自行推导变量的类型。

创建类型时可能会遇到:

  1. 类型难于拼写

  2. 含义不明确导致容易出错

    因此c++定义了一个auto关键字

根据右边的表达式自动推导类型

int a =0;

int b =a;

auto c=a;

auto d= 1+1.1;//自动获取等号右边的类型
//在类型很长的时候使用,短的时候使用不方便
cout << typeid(b).name() << endl;//typeid可用来查看变量类型
cout << typeid(c).name() << endl;

typeid:打印类型

cout<<typeid(c).name()<<endl;

image-20250501213907065

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

auto与指针和引用的结合

int x=10;
auto a=&x;
auto* b=&x;//指定右边类型为指针,不是指针就报错了
auto& c=x;//指定为引用

限制:

  • 不能作参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
  • 不能在同一行定义多个变量
 auto a = 1, b = 2; 
auto c = 3, d = 4.0;  // 该行代码会编译失败,因为c和d的初始化表达式类型不同
  • 不能用来声明数组
auto b[] = {4,5,6};

1.2范围for的使用:

  • 常规c++打印数组
void TestFor()
 {
 int array[] = { 1, 2, 3, 4, 5 };
 for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
 array[i] *= 2;
 }
 for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
 cout << *p << endl;
  • 用范围for打印数组:适用于所有数组
void TestFor()
 {
 int array[] = { 1, 2, 3, 4, 5 };
    //依次将array数组里的值赋值给e并且可以自动迭代,自动判断结束
 for(auto& e : array)//e是元素名称可以随便取名,auto也可改为int,double,但auto更合适方便修改类型,这里不加引用&时,修改其值对数组里的值没有影响,所以加上&就能直接对数组进行修改
 e *= 2;
 for(auto e : array)
 cout << e << " ";
 return 0;
 }

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

是不是变得很简单了,这就是范围for的作用

范围for的使用条件
for循环迭代的范围必须是确定的
迭代的对象要实现++和==的操作
对数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end方法,begin和end就是for循环的迭代范围。范围for本质上是迭代器,支持迭代器就支持范围for。

void TestFor(int array[])//这里不能传数组,因为会退化为指针
 {
 for(auto& e : array)//不能使用范围for
 cout<< e <<endl;
 }

在 C++ 中,当函数参数以数组形式 int array[] 传递时,实际上数组会退化为指向首元素的指针 int* 。而基于范围的 for 循环(for(auto& e : array) 这种形式 )要求操作数是一个有确定范围的对象,指针类型无法直接用于基于范围的 for 循环,因为它没有提供像数组那样明确的元素范围信息 ,所以这样写会报错。

正确写法应该传携带范围信息的变量,例如传size或者传int (&array)[5]

2.内联函数

内联函数(inline function)是一种向编译器发出的请求,建议编译器在调用该函数的地方直接将函数体代码展开,而不是进行常规的函数调用操作。这样做的目的是减少函数调用的开销,提高程序的执行效率,不过这仅仅是一个建议,编译器有权决定是否采用内联展开。

调用函数要建立栈帧有消耗,

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

写为宏函数形式:

#define Add(x,y) ((x)+(y))*10//不加分号,宏是一种替换不需要类型,需要传参,如果不把 x 和 y 括起来,宏展开时可能会出现运算符优先级问题,导致结果不符合预期。

如果不要求×10,也要加括号,如果不加括号这里的结果就变成10+20*20了,因为宏是一个替换,我们需要的是 (10+20)*20

Add(10,20)*20

如果是表达式

Add(a | b,a & b);
#define Add(x,y) (x+y)//这样写是错误的,直接替换为(a|b+a&b)后这里的+比|和&的优先级更高,所以先执行+在执行|和&

正确写法

#define Add(x,y) ((x)+(y))

优势:不需要建立栈帧,提高调用效率,可维护性(方便修改)

劣势:复杂,容易出错,可读性变差,不能调试,没有类型安全的检查

但是还是很容易出错,所以c++定义了一个内联函数(inline)

适用于短小的频繁调用的函数

inline对于编译器仅仅只是一个建议,最终是否成为inline,编译器自己决定

像类似函数就加了inline也会被否决掉

1.较长的函数

2.递归函数

inline Add()
{
}

太长的(全部展开)会导致代码膨胀,导致可执行程序变大

image-20250423105852127

出现call就没有成为内联

默认debug下,inline不方便调试,所以不会起作用,改为release就行了,但是release不方便看汇编

不是内敛:相当于10000个call加func

内联相当于内部展开50个10000的func

这里call了Add

image-20250423112610842

这里没有call Add就展开了

假如加上一串cout<<1111111<<endl,内联就不会起作用,长函数编译器不敢使用内联

声明有inline,定义无inline

在 C++ 中,若将内联函数的声明和定义分别放在不同的文件里,可能会出现链接问题。内联函数的定义通常需要在每个使用它的编译单元中可见,因为编译器要在调用点展开函数体。如果定义和声明分离,在编译时,编译器可能只看到声明而没有看到定义,无法进行内联展开,这就可能导致链接错误或者未定义行为。

  • inline是一种以时间换空间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。缺陷:可能会使目标文件变大,优点:少了调用开销,提高程序运行效率。
  • inline对编译器而言只是建议,不同的编译器关于inline的实现机制可能不同,一般建议:将函数规模小的(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
  • inline建议函数声明和定义不能分离,因为内联函数在预处理阶段就直接展开,因此内联函数不会进符号表,因此如果声明和定义分离,头文件只有声明,在预处理阶段,头文件展开,只知道该函数是一个内联函数,没有对应函数的定义,因此就无法完成替换,那就只能等通过call在链接阶段去找该函数,但是它是内联函数,没有进符号表,所以链接阶段就会报错。

声明和定义分离找不到定义时就会去链接找,但是没有call指令,也没有它的地址

内联函数没有函数地址不需要被call,内联函数不会进符号表

正确做法:直接定义在.h

可以在同一个项目的不同文件内定义函数名相同但实现不同的inline函数

因为inline函数会在调用的地方展开,所以符号表中不会有inline函数的符号名,不存在链接冲突。

假设一个函数经过编译,得到五十条汇编指令。普通情况下,调用此函数只需要一条call指令,调用10000此也就10000条call指令,但是如果把这个函数设置成内联函数,指令的数量就会大大增加,因为内联函数完成的是替换,把所有调用它的地方,都用函数体去替换,这也就意味着,原来1条call指令就能完成的任务,现在替换后就变成了50条指令,假如还是调用了10000次该函数,那就从10000条call指令,变成了500000条指令,其实这就是代码膨胀。

inline int Add(int x, int y)
{
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	cout << "xxxxxxxxxxxx" << endl;
	return x + y ;
}

int main()
{
	int ret = 0;
 	ret = Add(3, 5);
	cout << ret << endl;
	return 0;
}

image-20250501220335661

这里我们用的是内联函数啊,为什么还是会call呢,因为这里函数比较长,即使我们把他定义为内联,但是还是取决于编译器。

频繁调用:

因为普通函数在调用的时候会创建函数栈帧,若频繁调用就会频繁的创建栈帧,增加消耗。宏和内联,就是为了解决开销问题。如果调用的次数不多,开辟一点栈帧是无所谓的。

3.空指针(nullptr)

image-20250423133753266

c++的空指针为nullptr

传null会被定义为0

c++没有规定必须要形参接收,只写类型也没错,不需要就可以不用写

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器 默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

注意: 1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入 的。

​ 2.在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

​ 3.为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值