你看到的真的是真实的么?你眼中的 const 和 auto 关键字

本文深入探讨C++中的const关键字、内联函数概念及其与C语言的区别,同时全面解析C++11新增的auto关键字、基于范围的for循环及nullptr指针空值的特性和使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

编译环境:VS 2013



所谓眼见为实,耳听为虚,然鹅,你可能也会被事情的表面蒙蔽了双眼,不信你看下面的代码:

#include <iostream>
#include <cstdlib>

int main(void)
{
	const int a = 10;
	int * pa = (int *)&a;

	*pa = 100;
	std::cout << a << std::endl;
	std::cout << *pa << std::endl;

	system("pause");
	return 0;
}

嗯…首先说明一下,这段代码编译没有问题,那么会输出什么呢?
A: 10 10
B: 10 100
C: 100 10
D: 100 100
好,排列组合了一下,选一个吧!
A? B? C? D?
接下来,就是见证奇迹的时刻:
运行结果
答案是 B 选项,为什么呢?

一、宏和 const

我们先回顾一下 C 语言中的宏定义宏函数

1. 宏定义和宏函数

我们说,如果某个常量在代码中多次出现,并且经常修改,那我们可以考虑在代码中定义一个宏,这样每次更换的时候,只需要更换这个宏的值,其余地方就跟着替换了,那这是一个好方法啊,宏的定义方式如下:
#define DEFINE_NAME DEFINE_VALUE
例如: #define MAX_SIZE 1024
当然对一些常用的简单函数也是可以把它定义为宏函数
例如:#define ADD(left, right) ((left) + (right))
宏在代码预处理阶段,进行全文替换,代码中凡是有该宏的地方,全部替换为该宏的值
那这样做的好处

  • 提高了代码的复用性
  • 提高性能

为什么说提高了性能呢?我们知道,调用一个函数是要压栈,形成该函数的栈帧结构的,这肯定是要花费时间的,是有开销的,这也是为什么递归函数运行很慢的原因,而宏只是在代码中进行了替换,就不会有函数调用和压栈开销了,自然就提高了性能。
但这样做也有坏处

  • 没有参数检验
  • 没法调试
  • 代码膨胀
  • 副作用

其他都好理解,这个副作用怎么理解呢?
我们看看下面一段代码:

#include <iostream>
#include <cstdlib>

#define CMP(left, right) (((left) > (right)) ? (left) : (right))

int main(void)
{
	int a = 20;
	int b = 10;
	int c = 0;

	c = CMP(++a, b);
	std::cout << a << std::endl;

	system("pause");
	return 0;
}

这段代码输出什么呢?
10? 20? 21?
我们来看看结果:
在这里插入图片描述
22?如果你大跌眼镜了,就说明你没有理解我们刚刚说的宏替换
其实代码已经变成这样了
c = (((++a) > (b)) ? (++a) : (b))
自增运算执行了两次,自然就是 22 了。
这就是宏的副作用,加再多括号都避免不了的。

2. C 语言中的 const 关键字

我们说,我们不想修改某个变量的值,可以用const 来修饰这个变量,使这个变量具有常属性
const int a = 10;
这样我们就无法直接修改变量 a 的值了;
但是我们却可以通过指针的方式间接修改 a 的值:
int *pa = &a;
*pa = 20;
这样就把 a 的值修改为 20 了,所以C语言中的 const 关键字是个假的

二、C++ 中的 const 关键字和内联函数
1. C++ 中的 const

其实C语言中的宏是一个非常不错的想法,加上 C++ 完全兼容 C 的特性,于是我们在 C++ 语言中对 const 有了新的含义,那就是负载了宏的特性,在C++中,const 修饰的变量就是一个常量,并且具有宏的特性
我们回头再看看篇首的代码…
其实是程序在编译期间,把所有出现 a 的地方替换为 10 了,第一个输出的就是10,我们通过指针的方式,还是间接修改了变量 a 所占地址空间的内容,也就是说 a 变量地址空间里存的是 100,而不是 10 了。我们替换的早,a 打印出来是 10,修改地址空间里的值是在程序运行期间修改的,*pa 打印出来的就是 100 了。

2. 内联函数

建议内联!建议内联!!建议内联!!!
我们说 C 语言在用宏函数的时候直接替换,不进行参数检验,这使得一个非常不错的想法有了隐患,调用者就要考虑副作用了,这无疑会让人脑壳疼
于是我们在 C++ 中引入了内联函数的概念

  • 有函数的结构,却不具备函数的性质
  • 不是在调用时发生控制转移
    而是在编译时将函数体嵌入在每一个调用处
    类似于宏替换,使函数体替换调用处的函数名
  • 用关键字inline修饰
  • 能否形成内联函数,需要## 标题看编译器对该函数定义的具体处理
    所以说,我们只是建议内联给编译器提个建议,建议把这个函数设置为内联函数,所以不是加了 inline 关键字的函数都能成为内联函数
  • 内联扩展是用来消除函数调用时的时间开销
  • 是一种用空间换时间的做法
    所以代码很长或者有循环/递归的函数不适宜作为内联函数
三、C++11 新规定的 auto 关键字
1. C++11 标准出来之前

C/C++中 auto 关键字的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它,为什么呢?我们想,局部变量开辟内存是在栈上,函数调用结束,栈自动销毁,那我还要这鸡肋的关键字干哈?我又不傻!!

2. C++11 中

标准委员会赋予 auto 全新的含义:
auto 声明的变量必须由编译器在编译时期推导而得
什么意思呢?
就是说定义一个变量,不用给出明确地类型,auto 就像一个“占位符”一样,编译器在编译阶段会根据需要初始化表达式来推导 auto 的实际类型

#include <iostream>
#include <cstdlib>

int TestAuto()
{
	return 0;
}

int main(void)
{
	int a = 10;
	auto b = 10;
	auto c = a;
	auto ch = 'c';
	auto d = 3.14;
	auto ret = TestAuto();
	
	std::cout << "The type of a is "<< typeid(a).name() << std::endl;
	std::cout << "The type of b is " << typeid(b).name() << std::endl;
	std::cout << "The type of c is " << typeid(c).name() << std::endl;
	std::cout << "The type of ch is " << typeid(ch).name() << std::endl;
	std::cout << "The type of d is " << typeid(d).name() << std::endl;
	std::cout << "The type of ret is " << typeid(ret).name() << std::endl;

	system("pause");
	return 0;
}

编译执行:
typeid().name()
我们通过 typeid().name() 打印出来了变量的类型
看来这个 auto 确实可以推导出变量的类型
下面我们再来看一下 auto 更多的使用规则:

3. auto 的使用规则
  • auto 与指针和引用结合起来使用
    用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别
    但是!!!用 auto 声明引用类型时则必须加 &
#include <iostream>
#include <cstdlib>

int main(void)
{
	int x = 10;
	auto a = &x;
	auto *b = &x;
	auto &c = x;
	
	std::cout << "The type of a is "<< typeid(a).name() << std::endl;
	std::cout << "The type of b is " << typeid(b).name() << std::endl;
	std::cout << "The type of c is " << typeid(c).name() << std::endl;
	
	*a = 20;
	std::cout << "x = " << x << std::endl;
	
	*b = 30;
	std::cout << "x = " << x << std::endl;
	
	c = 40;
	std::cout << "x = " << x << std::endl;


	system("pause");
	return 0;
}

编译执行:
在这里插入图片描述
三者比较刚好证实了我们的结论!

  • 在同一行定义多个变量
    这些变量必须是同类型的,否则编译器报错
    编译器只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
4. auto 不能推导的场景
  • auto 不能作为函数的参数
  • auto 不能直接用来声明数组

好,我们了解了 auto 这个关键字,然后发现,它还是个鸡肋!!!
要是 auto 只有这么点功能,标准委员会也不会给他重新赋予新含义了
它的魅力在于:

四、C++11 的基于范围for循环
1. 范围 for 的语法
for (declaration : expression)
    statement
  • expression 部分
    是一个对象,用于表示一个序列
  • declaration
    负责定义一个变量,该变量将被用于访问序列中的基础元素
    这个变量根据自己需要,决定是否定义为引用
  • 每次迭代,declaration 部分的变量会被初始化为 expression部分的下一个元素值

例如,遍历一个数组:

#include <iostream>
#include <cstdlib>


int main(void)
{
	int array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };

	// 传统遍历数组的方法
	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)
	{
		std::cout << *p << " ";
	}
	std::cout << std::endl;

	// C++11 的范围 for 
	for (auto e : array)
	{
		std::cout << e << " ";
	}
	std::cout << std::endl;

	system("pause");
	return 0;
}

这样的 for 循环,对于遍历一个有范围的集合是不是很方便呢?还不用判断边界条件!!!

2. 范围 for 的使用条件
  • for 循环迭代的范围必须是确定的
    对数组而言,范围就是数组中的第一个元素到最后一个元素
    对于类而言,这个类必须提供 begin 和 end 方法,begin 和 end 就是 for 循环迭代的范围!

  • 迭代的对象要实现 ++ 和 == 的操作

五、C++11 的指针空值 nullptr
1. C++98 中的指针空值

对指针这个东西,我们是一点都不陌生,在定义一个指针的时候,我们常常有这样的操作:
int *p = NULL;
或者
int *p = 0;
因为使用未经初始化的指针是引发运行时错误的一大原因
对于常量 NULL
有的头文件这样定义: #define NULL 0
有的头文件这样定义: #define NULL ((void *) 0)

2. C++11中的 nullptr

nullptr 是一种特殊类型的字面值,它可以被转换为任意其他类型的指针类型。
我们看这样一段代码:

#include <iostream>
#include <cstdlib>

void func(int a)
{
	std::cout << "void func(int)" << std::endl;
}

void func(int *p)
{
	std::cout << "void func(int *)" << std::endl;
}

int main(void)
{
	func(0);
	func(NULL);
	func(nullptr);

	system("pause");
	return 0;
}

编译执行:
重载函数
通过这个重载函数我们看到,对 NULL 的处理本应该是参数为指针的 func 函数,却变成参数为整型的 func 函数,而实参为 nullptr 成功调用参数为指针的 func 函数
在新标准下,现在的 C++ 程序建议使用 nullptr ,同时尽量避免使用 NULL

3. decltype

我们再来看一个 C++11 新标准引入的第二个类型说明符 decltype
,它的作用是选择并返回操作数的数据类型
编译器分析表达式并得到它的类型,却不实际计算表达式的值
decltype(f()) sum = x; // sum 的类型就是函数 f 的返回类型

4. nullptr_t

nullptr 是有类型的,其类型为 nullptr_t,仅仅可以被隐式转换为指针类型。
typedef decltype(nullptr) nullptr_t;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值