C语言中那些后知后觉的细节冷知识(一)static、const、volatile、数组、宏

文章目录

  • 前言
  • 一、数组初始化
  • 二、volatile关键字
  • 三、static关键字
  • 四、const关键字
  • 五、宏定义参数
  • 总结

前言

C语言诞生于1972年11月,由美国电话电报公司(AT&T)贝尔实验室的丹尼斯·里奇(Dennis Ritchie)发明,在1978年,美国电话电报公司(AT&T)贝尔实验室正式发布了C语言。C语言作为一种经典的编程语言,时至今日,仍然在许多领域仍然被广泛使用,并且仍然是一种强大、高效和灵活的编程语言。
学习C语言对于初学者来说可能有一定的难度,因为C语言是一种比较底层的编程语言,它提供了更接近计算机硬件的控制能力。与一些更高级的编程语言相比,C语言在语法和概念上可能更加复杂,需要更多的时间和精力来学习和掌握。从本文开始,我会分享一些C语言的细节冷知识,希望对大家有所帮助。


一、数组初始化

数组初始化可以说是非常简单也很常用的操作,一般来说包括以下几种方式:
1、定义时初始化
数组在定义时就进行初始化,这个比较常见,最常用在把数组元素全部初始化为0的时候。
比如,以下代码会将数组a的所有元素都初始化为0:

int a[10] = {0};

拓展一下:

int a[10] = {1};

上述代码中,初始化后的数组a是不是所有元素均为1呢?答案是否定的。只有a[0]会被初始化为1,而a[1~9]会被默认初始化为0,这点需要注意。
2、按字节初始化
对数组按照字节进行初始化,主要是使用memset函数进行,主要使用场景是数组元素被初始化为同样的值。
比如:

unsigned char a[100];
memset(a,123,sizeof(a));

上述代码中,初始化后的数组a[0~99]均被初始化为123。
3、循环遍历初始化
用循环遍历的方式对数组进行初始化,主要使用场景是数组元素被初始化为非0,且初始值有一定的规律的时候。
比如:

int i;
int a[10];
for( i = 0; i < 10; i++ )
{
	a[i] = i + 1;
}

上述代码中,初始化后的数组a[0~9]分别被初始化为1 ~ 10。

注意以上每种数组初始化方式的应用场景。一般来说,如果是将所有数组元素均初始化为0,推荐使用第一种方式(书写简单,执行效率也高);如果不嫌书写麻烦的话,第二种方式也可以,执行效率相差不大;第三种方式的循环遍历就不推荐了,如果数组很长,循环遍历会执行很长的时间,不到万不得已,不要使用。

二、volatile关键字

在C语言中,volatile关键字用于告诉编译器该变量可能会在任何时候被意想不到地改变,因此,编译器在生成代码时不应对此变量进行优化,使得在用到这个变量时必须每次都重新从内存中读取这个变量的值,而不是使用保存在寄存器里的备份。
想一想以下代码有什么问题:

//求平方
int square(volatile short *ptr)
{
	if( ptr == NULL )
	{
		return -1;
	}
	return (*ptr) * (*ptr);
}

ptr被volatile修饰,ptr的值可能被意想不到地改变,因此前一个ptr和后一个*ptr可能是不同的值,计算出来就可能不是平方值。要想达到求平方的目的,应做如下修改:

//求平方
int square(volatile short *ptr)
{
	int tmp;
	if( ptr == NULL )
	{
		return -1;
	}
	tmp = *ptr;
	return tmp * tmp;
}

三、static关键字

C语言中static关键字的主要作用有:限制变量和函数的作用域;定义静态变量;在函数中保持变量的值等。需要特别注意的就是在函数内部定义的局部静态变量,和普通的局部变量有着很大的不同,局部静态变量只会被初始化一次,此后其值会一直保持,不会像普通的局部变量一样,每次函数调用都会重新定义。

int sum_fun(int a)
{
	static int b=1;
	int c = 1;
	b+=1;
	return(a+b+c);
}
void main()
{
	int i;
	int a=2;
	for(i=0;i<5;i++)
	{
		printf("%d,", sum_fun(a));
	}
}

执行上述代码会输出:5、6、7、8、9

四、const关键字

在C语言中,被const修饰的变量不可修改,意味着“只读”。
思考一下以下代码中的定义有什么区别:

const int a;		//1
int const a;		//2
const int *a;		//3
int * const a;		//4
const int *const a;	//5

1和2的作用是一样,都表示a是一个常整型数。
3表示a是一个指向常整型数的指针(整型数不可修改,但指针可以修改)。
4表示a是一个指向整型数的常指针(指针指向的整型数可以修改,指针不可修改)。
5表示a是一个指向常整型数的常指针(指针指向的整型数和指针本身均不可修改)。

五、宏定义参数

宏定义在C语言被广泛使用,主要用于为代码中的常量、函数或复杂的表达式定义一个简化的名称。但是宏定义只是简单的文本替换,它不进行任何类型检查,因此,在使用宏定义时需要特别小心,以避免产生不易察觉的错误。
想一想以下代码有什么问题:

#define  abs(x)  x >= 0 ? x : -x

上述代码在执行“c = abs(a-b);”语句时,宏替换后变成“c = a-b >= 0 ? a-b : -a-b;”,当a-b小于0时,结果错误。应做如下修改:

#define  abs(x)  (((x) >= 0) ? (x) : -(x))

可见懂得把宏定义参数用括号括起来是多么的重要。


总结

以上都是一些C语言的基础,但却又是平时编程中很容易因为疏忽出现问题的地方。更多的细节冷知识将在后续系列文章中持续更新,希望对大家有所帮助。