龙空技术网

c语言解剖课:复合字面量和匿名数据的那些事儿

段誉和语言 78

前言:

目前大家对“c语言与c语言相同点”大约比较讲究,咱们都想要剖析一些“c语言与c语言相同点”的相关知识。那么小编也在网上收集了一些关于“c语言与c语言相同点””的相关知识,希望看官们能喜欢,你们快快来了解一下吧!

写在前面

在我的上一篇文章《c语言解剖课:只读变量、常量、字面量傻傻分不清?》中,详细剖析了只读变量、常量、字面量的异同,今天我们来剖析下组合字面量和匿名数据的那些事儿。

学习C语言,或其他任何一门编程语言,概念之间相似、等价、相同这三个维度,我个人认为如果是在软件开发项目中是不需要去较真的,解决问题就好。

但是,如果想提升自己的技术理解力、洞察力、解剖力,较真就很有必要,因为既然有差异,就肯定是有原因的,较真让我们不断的接近真相。

之所以在前面写下这些,是因为有粉丝说我的技术文章写的太细,就算搞不清楚这些概念,也不影响成为程序员,因此有感而发,不吐不快,欢迎百家争鸣。

字面量

字面量,因为昨天发的文章里已经有详细的解剖,所以我们简单总结下:

类似数字2、3.5、1.8F,字符‘c'、字符串“ok”这些数据,具有以下特点:以本身数值形态呈现并,其数据类型是编译器通过其数值形态自动推导的、没有类似于常量名、变量名的字面量名(无名或匿名)、存放在只读区域,这样的数据我们称为字面量,英文是Literal,翻译成中文,就是“字面意义的”、“本来意义的”,

字面量的最大特点就是同一个字面量不能被重复使用。比如:

int x =2;int y = 2;

这行代码里,给变量x和变量y赋值的是两个不同的字面量,是的,你没有看错。

类型和值都相同,第一个2和第二个2都被分配在只读空间区域,但是却被存放在两个不同的内存地址里,所以是2个不同的字面量。

复合字面量

多个字面量组合在一起 就是 复合字面量,英文名是 compound literals,或者组合字面量,是聚合类型的一种。

就是把多个字面量通过花括号组合在一起,构成一个初始化列表的形式,然后给其他对象进行赋值或初始化。

这样做的好处很多,可以使代码灵活、简洁。举几个例子,演示一下:

int array[] = {1,2,3};int *ptr = array;

通过复合字面量用法,简化如下:

int *ptr = (int[]){1,2,3};

除了指针变量以外,如果你打算给数组也用复合字面量的方式初始化,你可能会困惑。比如:

int array[] = (int[]){1,2,3};

让我们在不同的编译器里编译一下,看看什么情况。比如在CLion2023里,默认的clang,c11标准,给出如下错误提示:

CLion2023,C11

error: array initialized from non-constant array expression

non-constant 是非常量的意思,而我们的复合字面量是常量表达式,array是变量。常量有什么特性?大家都知道,常量的生命周期贯穿于程序的整个运行期间,而array是变量,变量要想和具备和常量一样的生命周期,必须用static修饰,我们修改一下:

CLion中static修饰

可以了?!说明我们的猜测是正确的?只要变量具备static属性就可以匹配复合字面量(常量表达式)?先不急着下结论,我们再在visual stdio 2022里(c11标准的MSVC编译器)测一下卡年。先不用static修饰,提示如下:

MSVC,C11

意料之中,我们再改成static类型的,再测一下:

还是不行?给出了“初始值设定项不是常量”的报错,数组是static类型,复合字面量也是常量表达式,怎么还是不行呢?

因为MSVC编译器比较严格,数组初始化列表(初始化表达式)是更规范的用法,所以它强制约定优先使用{1,2,3},而且确实比(int[]){1,2,3}更为简洁。

但是static在CLion中又确实可以,那么到底static是不是规范用法呢?

也是。GCC明确规定了,变量如果具有static属性,其和常量一样的生命周期,将可以被复合字面量初始化。那就是VS又一次不按标准来干。

说到vS不按标准规范来,是众说周知的。比如c99明确规定了允许数组元素的数量可以是变量,比如:

在CLion中数组长度动态定义,没有任何问题(C99标准),但是咱们“宇宙第一IDE”,却是这样的:

还没编译就开始警告,我们不管他,强行编译,然后这样了:

好吧,不管VS了。回头主题,具有static特性的变量,GCC是允许复合字面量初始化的,但是实际上除了数组外,并不是必须要static修饰才行,比如:

指针p和结构体变量test都不需要static修饰。所以之前的猜测是不准确的。

我们总结一下:数组初始化直接用更规范的数组初始化表达式,其他变量如有初始化器(初始化表达式)的,优先采用。比如结构体,既可以用复合字面量的形式赋值,也可以用初始化器直接初始化。比如:

Test foo = { 2 };

和数组一样可以直接初始化,与static并没有太大的关联。

除了普通指针外,数组指针也可以用复合字面量(也可以称为聚合类型)初始化或赋值:

int array[] = {1,2};int (*ptr)[2] = array ;

可以如下简化:

int (*ptr)[2] = (int[]){1,2};

指向多个数组的数组指针:

int array[][2] = {{1,2},{3,4},{5,6}};int (*ptr)[2] = array;

用复合字面量简化一下:

int (*ptr)[2] =(int[][2]){{1,2},{3,4},{5,6}};

我们观察复合字面量会发现,其实就是前面小括号里是这个复合字面量的类型,注意,不是花括号里每个字面量的类型,而是整个复合字面量的类型,后面花括号里是所有字面量元素。

刚才拿数组举例子,很容易让人误解,以为组合字面量里每个元素都是类型相同的,实际上不一定,比如用符合字面量的方式,给结构体赋值或初始化一下:

struct _pos{	int x;	float y[2];};struct _pos pos = (struct _pos){2,{1.5,2.5}};

花括号里可以是不同类型的字面量。后来为了简化写法,也给结构体准备了类似数组一样的designated initializer,即初始化器(随便你怎么称呼吧,比如初始化列表、初始化表达式),所以直接这样写,可能更省事些:

typedef struct{	int x;	float y[2];}POS;POS pos ={2,{1.5,2.5}};

其实,函数参数才是复合字面量使用最广泛的地方。比如下面代码:

int sum(const int array[],int n){    int sum = 0;    for(int i=0;i<n;i++){        sum += array[i];    }    return sum;}int main(){    int array[] = {1,3,5};    printf("%d\n",sum((int[]){1,3,5},3));    return 0;}

通过复合字面量的用法,把main函数里的sum函数改写如下:

sum((int[]){1,3,5},3)

会很方便和灵活。

前面的所有组合字面量的用法都可以在函数参数里使用,节省了先定义对应类型的变量,再在函数参数里使用的步骤。

匿名数据

字面量、复合字面量因为没有显式的“字面量名称”,实以这样的数据,一般称为“匿名”数据(或无名数据)。匿名数据最大的特点是不能在程序中被反复使用。

比如你定义了一个匿名数据:(int[]){1,2,3}; 之后你将无法再次使用它。比如下面的情况:

(int[]){1,2,3};int *p = (int[]){1,2,3};

第一个(int[]){1,2,3}和第二个(int[]){1,2,3},是两个不同的匿名数据。

普通变量的定义,比如:int x = 2;也可以写成复合字面量的形式,比如:

int x = (int){2};

虽然符合语法,编译运行都可以,但是我们一般不会这样写,是吧?注意,不要和强制类型转换混淆了,别写成这样了:

int x = (int)2;

下面是一些常见的直接使用字面量作为函数参数的例子:

func(“hello”);func(‘c’);func(3)

使用复合字面量入参,前面已经介绍过了。

最后,我们简单提一下“匿名”的概念,其实所有编程语言中,都有这个概念。

除了字面量、复合字面量,匿名数组、匿名结构体、匿名共用体、匿名对象、匿名指针,c++中还有匿名空间,等的功能。

在c++中,会与c有些不同,复合字面量是左值,但是c++目前没有匿名左值。而且,c++把复合字面量作为临时对象对待,其所在的完整表达式一经结束,即被销毁,而c语言中和常量一样是持续存在的。

本篇文章主要是基于C语言范围的解剖系列,c++方面不再展开。

段誉,2024年1月31日,写于合肥。

标签: #c语言与c语言相同点