复杂数据类型
复杂数据类型主要包括数组类型、枚举类型、结构体、联合体。可以通过这些类型定义很多复杂的用户自定义的数据结构。如我们需要描述一个用户的属性,用户可能会有比较复杂的属性需要描述,姓名、性别、年龄、身高、是否有钱等,还有很多,当然这些都是自定义的,假如我们就描述上面的这些用户属性,可以通过一个结构体来定义这些用户属性:
struct ut
{
char* name; // 姓名
int male; // 性别
int age; // 年龄
int height; // 身高
int toff; // 是否有钱
};
前面说过,每种数据类型都有其大小,复杂数据类型都是用户自定义的,那么它们大小是多少?这个在介绍每种复杂数据类型再详细讲解。
数组
数组是一组某种数据类型的连续内存空间。比如我们常见的字符串,我们就可以通过数组表示:
char name[] = "abc";
在定义数组类型时,需要指定这个数组的数据类型以及数组的长度。这个数组的数据类型可以是任何数据类型,包括复杂数据类型,比如结构体类型,甚至是数组类型,也就是数组的数据类型还是数组类型。
数组声明
WG14/N1256中对数组声明的形式如下:
标准中对数组声明的形式还挺复杂,包含4种形式。整个声明包括两个部分,即上面的T以及D1。其中D1存在4种形式,且每种形式都有一个type-qualifier-list和assignment-expression,注意上面的opt下标表示可选。这里的type-qualifier-list和assignment-expression不太容易理解。参考type-qualifier-list,这里可以指定一些类型限定符,如const、restrict以及volatile,这在我们声明一个数组的时候很少见到。还有这里为什么会是一个assignment-expression,这是一个赋值表达式,我们常见的要么在中括号中没有指定长度,要不就是一个常量表达式表示数组长度。
第1种形式:
其中type-qualifier-list和assignment-expression是可选的。
如char a[]="abc";当有初始化的时候,可以不用指定数组长度。
如果指定type-qualifier-list
char a[const]
这种形式没发现哪里用得到。
如果指定assignment-expression,这种形式用于变长数组
int i;
char a[i = 10];
这里的数组a的定义域是块定义域(block scope)。
从这里来看的话,就能开始理解这里为什么会是一个assignment-expression。
int n = 3;
char a[const n];
这里的数组a的定义域是块定义域(block scope)。
void fn(int len, int a[len])
{
}
这里的数组a的定义域是函数原型定义域(function prototype scope)。
完整形式的例子
int i;
char a[i = 10];
这里的数组a的定义域是块定义域(block scope)。
第2种形式:
在这种形式中,type-qualifier-list是可选的,assignment-expression是必选的。
variable length array
简称“VLA”,也就是变长数组。
variably modified
简称“VM”。这个不太好翻译,翻译成“可变可修改的”,或者翻译成“易变可修改的”,反而不容易理解。
declarator is a sequence point. If, in the nested sequence of declarators in a full declarator, there is a declarator specifying a variable length array type, the type specified
by the full declarator is said to be variably modified. Furthermore, any type derived by
declarator type derivation from a variably modified type is itself variably modified.
function prototype scope. Array objects declared with the static or extern storage-class specifier cannot have a variable length array (VLA) type. However, an object declared with the static storageclass specifier can have a VM type (that is, a pointer to a VLA type). Finally, all identifiers declared with a VM type have to be ordinary identifiers and cannot, therefore, be members of structures or unions.
这里要注意的是,易变可修改类型的变量必须是块定义域(block scope)或者函数原型定义域(function prototype scope)。
在定义数组变量时,如果没有初始化的话,必须指定数组长度
char a[];
error: array size missing in ‘a’
数组长度可以显式指定,或者有隐式的指定数组长度,比如上面的例子,这个在后面再详细讲。如果定义的是一个全局数组,不指定数组长度是可以的,但编译时会有一个警告:
char a[];
int main()
{
printf("addr: %u\n", a);
return 0;
}
warning: array ‘a’ assumed to have one element
一些例子:
char name[8];
short n[8];
int n[8];
long n[8];
long long n[8];
float n[8];
double n[8];
甚至上面的struct ut类型:
struct ut vl[8];
数组长度
数组类型和其他数据类型不同的是,数组有个数组长度,这个跟其他数据类型是有本质区别。数组长度是指这个数组中可以存放多少个元素。比如char c[8];那么这个数组中最多可以存放8个字符;再比如int n[8];那么这个数组中最多可以存放8个int类型的整数,其他的类似。
数组长度不可以为负数
char a[-1];
error: size of array ‘a’ is negative
但数组长度可以为零(0),这种就是个零长度的数组:
char a[0];
零长度的数组size也为0,即不占用内存。
需要说明的是,在指定数组长度的时候,不可以是变量,比如:
int len = 8;
char name[len];
这个在C语言中是不允许的。
上面提到,在有些地方不需要显式指定数组长度,如上面的例子:
char name[] = "abc";
初始化“abc”其实隐式的指定了数组长度为4。
数组类型的大小
数组类型的大小其实涉及到很多细节的。总体来说,数组类型的大小和数组的数据类型和数组长度有关。基本可以这样计算:
size = sizeof(数组类型) * 数组长度
如:
char s[8];
那么这个数组的大小为8。
int i[8];
那么这个数组的大小为32。
其他的类似。
但也需要说明的是,上面都是我们显式指定数组长度的情况。如果是隐式的情况下,那么数组长度为实际赋值的元素个数。如:
int i[] = {1, 2, 3, 4, 5};
那么这个数组的大小为20。
针对字符串的特殊情况:
还是上面的例子:
char name[] = "abc";
其实上面已经给出了一些答案,那就是数组长度为4。那么数组大小也为4。但为什么数组长度为4呢?这个字符串不就3个字符么?
我们知道字符串是有限个字符串起来的,有起始字符和结束字符,在从内存中看我们并不知道结束字符到哪里结束,所以在内存中有一个结束符来表示字符串的结束,那就是'\0'。
这个结束符本身不是字符串的一部分,仅代表字符串的结束,但也占用1个字节。所以数组需要4个字节来存放这个字符串。也就是数组长度为4。字符串长度为3。
字符数组大小、字符数组长度以及字符串长度
这个经过上面的讲解其实这三个的区别已经很清楚了。
数组大小和数组长度
这个经过上面的讲解其实这两个的区别已经很清楚了。
变长数组
C语言标准以前是不支持变长数组(VLA, variable-length array)的。不过在一些编译器中早就支持这种数组。
如
int len = 8;
char s[len];
访问数组中的元素
通过数组变量名和下标的方式来访问数组,形如array[i];其中array表示数组变量名,i表示下标,表示访问第i个元素,i从0开始,依次类推,最后一个元素的下标为数组长度-1.注意不可以越界。
有些编译器对于数组访问越界错误提示很不明显,如下:
Abort trap: 6
枚举
enum
enum week { MONDAY, // Monday TUESDAY, // Tuesday WEDNESDAY, // Wednesday THURSDAY, // Thursday FRIDAY, // Friday SATURDAY, // Saturday SUNDAY // Sunday };
在gcc下,枚举类型占4字节。
结构体
struct
结构体在C语言是一种最常用的复杂数据类型。通过将多个其他数据类型组织在一起定义一个独立的更复杂的数据结构。其中的每一个在结构体中是一个字段。
结构体中的每个字段按照其定义的顺序依次占用一定的内存大小,大小由其字段类型决定。如int a;那么a字段占用4个字节。第1个字段的起始位置从结构体在内存中的地址开始偏移0的位置开始,第2个字段的起始位置依次在第1个字段后面,依次类推。
先定义一个简单的结构体:
struct st
{
int a;
int b;
int c;
};
这样定义了这个结构体之后,就可以定义这种结构体变量:
struct st v1;
这个结构体的大小为12,也就是sizeof(a) + sizeof(b) + sizeof(c) = 4 + 4 + 4 = 12。
结构体包含多个成员。
在GCC中,结构体中可以没有成员,即结构体是空的(0个成员)。
struct st
{
};
这在gcc中是可以的。
但在VC中,结构体中至少得包含一个成员。所以上面的结构体定义是错误的。如果是空的话,会报语法错误:
error C2059: syntax error
数组长度可以指定为零(0),表示零长度数组,在结构体中也可以有零长度数组
struct st
{
char a[0];
};
struct st
{
char a[0];
int i;
};
结构体中零长度数组不占用内存。
数组可以指定长度,但在结构体中,如果是数组成员,可以不指定数组长度,这种称为flexible array,即伸缩数组
flexible array
struct st {
int i;
char a[];
};
在这个例子中,结构体中有个数组成员,这个数组没有指定长度,也就是个伸缩数组。
如果结构体中存在伸缩数组,结构体至少还有一个其他成员
struct st {
char a[];
};
error: flexible array member in otherwise empty struct
这里所说的“结构体至少还有一个其他成员”指的是其他成员不是伸缩数组
struct st {
char a[];
char b[];
};
error: flexible array member not at end of struct
另外,伸缩数据必须是结构体的最后一个成员
struct st {
char a[];
int i;
};
error: flexible array member not at end of struct
结构体中存在伸缩数组的初始化
struct st st1 = {1, {'a', 'b', 'c'}};
struct st st2 = {1, "abc"};
通常,我们可以通过sizeof来获得结构体的大小,并且根据结构体的定义,我们也可以知道这个结构体的大小。这个结构体的大小在编译期就是已经确定了的,确切的说在编译期前,就已经确定下来了的。
但如果结构体中存在伸缩数组,通过sizeof无法获知结构体的大小,根据sizeof获得的结构体大小是不包含伸缩数据字段的大小的,也就是在这个时候伸缩数据字段是不占用内存的。
实际内存占用是在初始化的时候决定的。
结构体在内存中的布局
如上面的例子,在定义了变量v1后,这个结构体变量在内存中的布局:
v
+--------+--------+--------+
|4bytes |4bytes|4bytes|
+--------+--------+--------+
^ ^ ^
|a | |
|b |
|c
结构体在内存中的布局可不是那么简单。看下面这个例子:
struct st2
{
char a;
int b;
double c;
};
其在内存中的布局可不是这样的:
|1byte |4bytes |8bytes|
+--------+--------+--------+
^ ^ ^
|a | |
|b |
|c
其真实内存布局是这样:
|4bytes |4bytes|8bytes|
+--------+--------+--------+
^ ^ ^
|a | |
|b |
|c
结构体大小
在上面的简单例子中:
struct st
{
int a;
int b;
int c;
};
似乎结构体大小就等于结构体中各字段大小之和。但是...
结构体大小其实也不是一言两语就能讲解清楚的。我们在上面有讲到结构体在内存中的布局。结构体大小其实跟这个结构体在内存中的布局有关。我们将在后面讲到,包括影响结构体在内存中的布局的另一个概念:内存对齐。
这里我们要想知道结构体大小到底是多少的话,暂且可以通过sizeof来获知结构体大小。这里先给个悬念:
struct st { char a; int b; int c; } __attribute__ ((aligned (2), __packed__));
这个结构体的大小是多少?
offsetof
offsetof (type,member)
这是一个函数宏,它返回数据结构体或者联合体类型中成员的偏移量:表示相对数据结构偏移的字节数。参数type可以是一个结构体、联合体、a POD class、a standard-layout class,member表示type的成员。
这个不是个标准函数,并不是所有编译器中的标准库都提供这个函数。如VC6.0。GCC支持。
struct entry { int key; int value; struct entry *next; }; void main(int argc, char** argv) { printf("%ld\n", offsetof(struct entry, key)); printf("%ld\n", offsetof(struct entry, value)); printf("%ld\n", offsetof(struct entry, next)); }
联合体
union
联合体在C语言是一种非常有用的复杂数据类型。和结构体一样,通过将多个其他数据类型组织在一起定义一个独立的更复杂的数据结构。其中的每一个在结构体中是一个字段。
和结构体不一样的是,联合体中的每个字段并不是按照其定义的顺序依次占用一定的内存大小,而且,联合体中的每个字段的起始地址都是从联合体在内存中的地址开始的。也就是说每个字段的起始地址都是一样的,和联合体在内存中的地址一样。按照这样的话,其中的每个字段的在内存中占用的内存空间是有一部分是重叠的。
当然其中的每个字段大小由其字段类型决定。如int a;那么a字段占用4个字节。
定义一个联合体和定义一个结构体差不多,只是一个是struct,一个是union。
先定义一个简单的联合体:
union ut
{
int a;
int b;
int c;
};
这样定义了这个联合体之后,就可以定义这种联合体变量:
union ut v1;
联合体在内存中的布局
union ut
{
char a;
int b;
double c;
};
联合体大小
联合体大小其实也不是一言两语就能讲解清楚的。
这里我们要想知道联合体大小到底是多少的话,暂且可以通过sizeof来获知联合体大小。
相关推荐
本章主要介绍了C语言中的三种复杂数据类型:结构体(struct)、共用体(union)以及枚举(enum),并且涉及到结构数组、指向结构的指针、线性链表以及复杂数据类型在函数中的应用。 1. 结构体(struct) 结构体允许...
### C语言数据类型详解 #### 引言 C语言作为一种广泛应用的基础编程语言,其数据类型的设计与使用至关重要。本文将详细介绍C语言中的各种数据类型及其特点,帮助读者更好地理解和运用这些基本概念。 #### C语言...
* 结构体类型(Structure):是一种复杂的数据类型,由多个基本数据类型组成,例如学生结构体、员工结构体等。 * 共用体类型(Union):是一种特殊的结构体类型,所有成员共享同一块存储空间。 指针类型(Pointer)...
### C语言中的结构体:构建复杂数据类型的艺术 C语言作为一种通用且强大的编程语言,在软件开发领域占据着举足轻重的地位。它以其简洁高效、接近硬件层、良好的可移植性和丰富的库支持等特点受到广大程序员的喜爱。...
C语言中的数据类型包括基本数据类型、构造类型、指针类型以及空类型(`void`)。这些数据类型的设计和定制受到多种因素的影响,例如计算机硬件架构、编译器特性以及语言的设计目的。 - **基本数据类型**:这是C语言...
在C语言编程中,数据类型和变量是构建任何程序的基础元素。C语言提供了多种基本数据类型,包括整数类型、浮点数类型和字符类型,它们各有特定的用途和存储需求。 1. 整数类型:C语言中的整数类型包括`int`、`short`...
本章主要讨论四种复杂数据类型:枚举类型、结构体、共用体以及链表,并介绍了如何使用`typedef`来定义类型别名。 **8.1 枚举类型** 枚举类型(enumerated type)用于定义一组具有特定名字的整数常量集合,这些名字...
本讲主要关注四种基本数据类型:基本数据类型、构造数据类型、指针类型和空类型。 1. **基本数据类型**: - 基本数据类型包括整型(int)、浮点型(float)、字符型(char)等,它们是最基础的不可分解的类型。...
C语言的基本数据类型及其运算 C语言的数据类型是指在C语言中所有数据的抽象表示,具有相同的形式,遵从相同的运算规则。C语言提供了丰富的数据类型,包括基本类型、整型、实型、字符型、标准类型、用户定义类型、...
构造数据类型包括数组、结构体和共用体,它们允许我们组合多种数据类型来创建更复杂的结构。指针类型用于存储内存地址,空类型void则用于声明没有具体数据类型的变量或函数参数。 2. **基本数据类型及其表示**: -...
### C语言基本数据类型详解 #### 一、引言 C语言作为一种强大的编程语言,广泛应用于操作系统、嵌入式系统等高性能计算领域。...接下来的章节将深入探讨更复杂的数据类型,如数组、结构体和共用体等。
总结,这个“第二章:数据类型与表达式”的PPT2涵盖了C语言编程的基础知识,包括数据类型、表达式、运算符、类型转换、复合数据类型、函数和指针。掌握这些概念对于理解和编写C语言程序至关重要。通过深入学习和实践...
1. 基本数据类型: - **整型(int)**:用于存储整数,如-123, 456等。C语言提供了不同字节的整型,包括`char`(通常为1字节),`short int`(通常为2字节),`int`(通常为4字节),和`long int`(通常为4或8字节...
C语言是计算机编程的基础语言,其第三章重点讲解了数据类型这一核心概念。数据类型是C语言中定义变量和常量的基础,它决定了变量能够存储的值的范围以及可以进行的操作。下面是对这部分内容的详细阐述: 1. **数据...
### C语言程序设计基本数据类型及运算 #### 一、引言 在计算机科学领域,C语言作为一种重要的编程语言,广泛应用于系统软件开发、嵌入式系统、游戏开发等多个方面。掌握C语言的基础知识对于程序员来说至关重要。本...
C语言提供了以下一些主要数据类型:整型、实型、字符型等。在C语言中,每个类型都定义了一个标识符,称为类型名。例如,整型用int标识、字符型用char标识等。一个类型名由一个或几个关键字组成,仅用于说明数据属于...
这些例题覆盖了C语言的基础语法、数据类型、控制结构、函数、指针、数组、结构体等核心概念,是提升编程能力的绝佳途径。 1. **基础语法**:C语言的基础包括变量声明、常量定义、赋值操作、输入输出等。例题可能会...
同时,它支持类型转换,允许不同数据类型的变量进行运算。 5. **移植性好**:由于C语言的底层特性,编写的程序可以在不同的操作系统和硬件平台上轻松移植,只要适配相应的编译器即可。 对于初学者来说,了解C语言...
在C语言中,数据类型是编程的基础,它们决定了变量如何存储和操作数据。第三章“数据类型、运算符和表达式”主要涵盖了以下几个关键概念: 1. **数据类型**:这是C语言中对变量类型的分类,它决定了变量的内存大小...