导读
这一期,我们将专门介绍C语言中的数据类型。
在C语言中,数据类型包含基本数据类型、枚举类型、空类型、指针类型、数组类型、结构体类型、联合类型。其中,基本数据类型包括整数类型、字符类型和浮点类型。
基本数据类型
//整数 类型
int //整型
short //短 整型
long //长 整型
long long //超长 整型
//字符 类型
char //字符 数据类型
//浮点 类型
float //单精度 浮点数
double //双精度 浮点数
注意:C语言中没有string类型,即字符串类型。
//各 基本 数据类型 所占 内存空间 大小,单位 字节
int--------------4
short----------2
long-----------4/8 //C语言标准规定:long 的 大小 >= int 的 大小。原则上只要满足规定即可,所以有的编译器long的大小会是8个字节。
long long----8
char----------1
float----------4
double-------8
枚举类型
在C语言中,枚举(Enumeration,简称enum)是一种用户定义的数据类型,它允许为整数常量指定一个名称,使代码更加可读和维护。枚举类型在声明时列出了一组命名的常量,这些常量的值默认是整型的,从0开始,每个后续的值比前一个值大1。但你也可以显式地指定每个枚举常量的值。
枚举的声明
下面是一个简单的枚举类型声明示例:
enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
在这个例子中,enum Days
定义了一个名为 Days
的枚举类型,它包含了七个枚举常量:Sun
、Mon
、Tue
、Wed
、Thu
、Fri
和 Sat
。这些常量的默认值分别是 0、1、2、3、4、5 和 6。
显式指定枚举常量的值
你也可以显式地指定枚举常量的值:
enum Months { Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec };
在这个例子中,Jan
的值被显式地指定为 1,因此 Feb
的值是 2,Mar
的值是 3,依此类推。
使用枚举类型
你可以在变量声明中使用枚举类型,如下所示:
enum Days day;
day = Wed;
enum Months month;
month = Oct;
枚举类型的限制和注意事项
- 枚举类型在C语言中是一种弱类型,枚举常量在大多数情况下被视为整数。这意味着你可以将一个整数赋值给枚举类型的变量,或者将枚举类型的变量与整数进行比较,但这可能会降低代码的可读性和可维护性。
- 枚举常量在编译时会被替换为它们对应的整数值。因此,如果你在代码中直接打印枚举常量的名称(如
printf("%s", Sun);
),这将会导致编译错误,因为枚举常量不是字符串。要打印枚举常量的名称,你需要使用其他方法,如使用字符串数组或switch语句。 - 枚举类型不会提供类型安全性,即你不能防止将一个与枚举类型不相关的整数赋值给枚举类型的变量。在C++中,可以通过使用强枚举类型(如
enum class
)来提供类型安全性。但C语言本身并不支持这种功能。
空类型
在C语言中,并没有直接称为“空类型”的类型定义,但有几个概念与“空”或“无”有关:
1.void关键字
void
是一个特殊的数据类型,表示“无类型”或“空类型”。它通常用于以下两种情况:
- 函数返回类型:如果一个函数不返回任何值,那么它的返回类型就是
void
。
void function_without_return() { | |
// 函数体 | |
} |
- 函数参数列表:
void
可以作为函数参数列表的占位符,表示该函数不接受任何参数。
void function_with_void_parameters(void) { | |
// 函数体 | |
} |
- 指针类型:
void *
是一种通用指针类型,它可以指向任何数据类型。然而,你不能直接通过void *
指针来访问它所指向的数据,你需要将它转换为合适的指针类型。
void *generic_pointer; | |
int x = 10; | |
generic_pointer = &x; // 合法 | |
printf("%d", *(int *)generic_pointer); // 需要显式类型转换 |
2.空语句
- 在C语言中,分号
;
本身就是一个空语句,它什么也不做。这通常用于满足语法要求,例如在循环或条件语句中。
//比如在循环语句中
while(控制条件){
;
}
3.空结构体
- C语言允许定义没有成员的结构体,这通常用于一些特殊的编程技巧,如作为占位符或作为实现特定功能的数据结构的基础。
struct EmptyStruct {}; |
但请注意,在C语言标准中,直接定义这样的空结构体可能是不合法的,因为它的大小可能是未定义的。然而,一些编译器可能允许这样的定义,并为其分配大小为0(尽管这在实际使用中可能是有问题的)。
4.空联合体 (Union)
- 类似于空结构体,C语言也允许定义没有成员的联合体,但这同样可能是编译器依赖的,并且可能在实际使用中导致问题。
5.空数组
- 在C语言中,直接声明一个大小为0的数组是不合法的。但是,你可以通过其他方式(如动态内存分配)来创建“空”数组。
6.空宏定义
使用 #define
预处理指令时,你可以定义一个空宏,它在被替换时什么也不做。
#define EMPTY_MACRO |
请注意,尽管这些概念与“空”或“无”有关,但它们并不构成一个专门的“空类型”。在C语言中,void
是最接近“空类型”的概念。
指针类型
在C语言中,指针是一个非常重要的概念,它允许程序直接访问和操作内存中的数据。指针变量存储的是内存地址,而不是数据本身。通过指针,我们可以对内存中的数据进行直接读写,从而实现更加高效和灵活的数据处理。
指针的定义
指针变量在声明时必须指定其指向的数据类型。例如,如果我们要声明一个指向整数的指针,我们可以这样写:
int *ptr; |
这里,int *
是一个指针类型,表示该指针指向一个整数。变量名 ptr
是这个指针变量的名称。
指针的初始化
在声明指针变量之后,我们通常需要将其初始化,即给它分配一个内存地址。我们可以将一个变量的地址赋给指针,例如:
int x = 10; | |
int *ptr = &x; // ptr现在指向x的地址 |
这里,&x
是一个取地址运算符,它返回变量 x
的内存地址。我们将这个地址赋给指针 ptr
,使得 ptr
指向 x
。
指针的解引用
一旦指针指向了某个变量,我们就可以通过解引用操作来访问或修改该变量的值。解引用操作使用星号 *
运算符,例如:
int y = *ptr; // y现在被赋值为x的值,即10 | |
*ptr = 20; // 现在x的值被修改为20 |
在第一个语句中,我们通过解引用 ptr
来获取它所指向的变量的值,并将其赋给 y
。在第二个语句中,我们通过解引用 ptr
来修改它所指向的变量的值,即将 x
的值修改为 20
。
指针的运算
指针变量之间可以进行一些基本的算术运算,如加法、减法等。这些运算的结果是以指针所指向的数据类型为单位的内存地址偏移量。例如:
int array[5] = {1, 2, 3, 4, 5}; | |
int *p = array; // p指向array的第一个元素 | |
p++; // p现在指向array的第二个元素 |
在这个例子中,我们将指针 p
初始化为指向数组 array
的第一个元素。然后,我们通过将 p
加1来使其指向数组的第二个元素。注意,这里的加1操作实际上是将指针的地址增加了 sizeof(int)
个字节(在大多数系统上,一个整数占用4个字节)。
指针的注意事项
使用指针时需要特别注意以下几点:
- 野指针:未初始化的指针变量被称为野指针。野指针指向的内存地址是不确定的,访问它可能导致程序崩溃或数据损坏。因此,在使用指针之前一定要确保它已经被正确初始化。
- 空指针:空指针是一个特殊的指针值,用于表示指针不指向任何有效的内存地址。在C语言中,空指针的值是
NULL
(在C99及以后的版本中,也可以使用nullptr
,但这不是C语言的标准关键字)。在将指针作为参数传递给函数时,如果该函数不需要访问指针所指向的数据,那么通常会将指针设置为NULL
。 - 指针越界:指针越界是指指针访问了不属于其指向对象的内存区域。这可能导致程序崩溃或数据损坏。因此,在使用指针时一定要确保不要越界访问内存。
- 动态内存分配:在C语言中,我们可以使用
malloc()
、calloc()
和realloc()
等函数来动态地分配内存空间,并将返回的内存地址赋给指针变量。在使用完动态分配的内存后,一定要使用free()
函数来释放它,以避免内存泄漏。 - 类型安全:由于C语言是一种弱类型语言,因此在使用指针时要特别注意类型安全。不同类型的指针之间不能随意转换和赋值,否则可能导致数据损坏或程序崩溃。在将指针转换为其他类型时,一定要确保转换是安全的,并且不会导致数据丢失或损坏。
数组类型
在C语言中,数组是一种基本的数据结构,用于存储固定数量的相同类型元素的集合。数组的每个元素都有一个唯一的索引,用于访问该元素。下面将对C语言中的数组进行详细介绍。
1. 数组的声明和初始化
在C语言中,你可以使用以下方式声明和初始化一个数组:
// 声明并初始化一个整型数组 | |
int arr[5] = {1, 2, 3, 4, 5}; | |
// 声明一个字符数组(字符串常量) | |
char str[] = "Hello, World!"; | |
// 声明一个未初始化的整型数组 | |
int uninitialized_arr[10]; |
在上面的例子中,arr
是一个包含5个整数的数组,str
是一个包含13个字符(包括结束符'\0')的字符数组,而 uninitialized_arr
是一个包含10个未初始化整数的数组。
2. 访问数组元素
你可以使用索引来访问数组中的元素。索引从0开始,到数组长度减1结束。例如:
int value = arr[2]; // value 现在被赋值为3 |
3. 数组的长度
在C语言中,数组的长度并不是数组类型的一部分。也就是说,你不能直接从一个数组变量中获取它的长度。但是,你可以使用 sizeof
运算符和数组的元素类型来计算数组的长度:
int len = sizeof(arr) / sizeof(arr[0]); // len 现在被赋值为5 |
注意:上述方法只适用于在声明时已知大小的数组。如果你有一个指向数组首元素的指针,并且没有额外的信息告诉你数组的大小,那么你将无法直接计算数组的长度。
4. 多维数组
C语言还支持多维数组,即数组的数组。多维数组在声明时需要使用多个方括号,并指定每个维度的大小。例如:
int matrix[3][4] = { | |
{1, 2, 3, 4}, | |
{5, 6, 7, 8}, | |
{9, 10, 11, 12} | |
}; |
在这个例子中,matrix
是一个3x4的二维数组(或称为矩阵)。你可以使用两个索引来访问它的元素,如 matrix[1][2]
(值为7)。
5. 数组作为函数参数
在C语言中,你不能直接将一个数组作为函数参数传递。但是,你可以传递数组的指针,该指针指向数组的首个元素。在函数内部,你可以通过指针来访问和修改数组的元素。例如:
void print_array(int *arr, int len) { | |
for (int i = 0; i < len; i++) { | |
printf("%d ", arr[i]); | |
} | |
printf("\n"); | |
} | |
int main() { | |
int arr[5] = {1, 2, 3, 4, 5}; | |
print_array(arr, 5); // 输出: 1 2 3 4 5 | |
return 0; | |
} |
在这个例子中,print_array
函数接受一个整数指针和一个整数长度作为参数,并使用这些参数来打印数组的元素。在 main
函数中,我们将数组 arr
的地址作为参数传递给 print_array
函数。由于数组名在大多数上下文中都会退化为指向其首个元素的指针,因此我们可以直接传递数组名。
结构体类型
在C语言中,结构体(Structure)是一种用户自定义的数据类型,它允许你将多个不同类型的数据项组合成一个单一的类型。结构体可以用来表示一个具有多个属性的复杂对象,比如一个学生、一个汽车或者一个日期等。
结构体类型的定义
结构体类型通过struct
关键字定义,后面跟着结构体的名称,以及一个由花括号{}
包围的成员列表。每个成员都包括一个类型和一个名称。
下面是一个表示学生的结构体的例子:
struct Student { | |
char name[50]; | |
int age; | |
float gpa; | |
}; |
在这个例子中,Student
是一个结构体类型,它有三个成员:name
(一个字符数组,用于存储学生的名字)、age
(一个整数,用于存储学生的年龄)和gpa
(一个浮点数,用于存储学生的平均成绩)。
结构体变量的声明和初始化
在定义了结构体类型之后,你可以声明该类型的变量,并可以对其进行初始化。
// 声明一个Student类型的变量 | |
struct Student student1; | |
// 初始化student1 | |
strcpy(student1.name, "Alice"); | |
student1.age = 20; | |
student1.gpa = 3.5; | |
// 或者在声明的同时进行初始化 | |
struct Student student2 = {"Bob", 22, 3.7}; |
结构体类型的别名
为了方便起见,可以使用typedef
关键字为结构体类型定义一个别名。这样,在声明结构体变量时就不需要每次都使用struct
关键字了。
typedef struct Student { | |
char name[50]; | |
int age; | |
float gpa; | |
} Student; // 这里Student就是struct Student的别名 | |
// 使用别名声明变量 | |
Student student3; | |
strcpy(student3.name, "Charlie"); | |
student3.age = 21; | |
student3.gpa = 3.6; |
结构体作为函数参数
结构体也可以作为函数的参数进行传递。当结构体作为参数传递时,实际上传递的是结构体的副本,而不是结构体的地址。因此,在函数内部对结构体的修改不会影响到原始的结构体变量,除非使用指向结构体的指针。
void printStudentInfo(Student s) { | |
printf("Name: %s, Age: %d, GPA: %.2f\n", s.name, s.age, s.gpa); | |
} | |
int main() { | |
Student student = {"David", 23, 3.8}; | |
printStudentInfo(student); // 输出学生的信息 | |
return 0; | |
} |
结构体数组
你也可以创建结构体数组,用于存储多个相同类型的结构体变量。
Student students[3] = { | |
{"Alice", 20, 3.5}, | |
{"Bob", 22, 3.7}, | |
{"Charlie", 21, 3.6} | |
}; |
结构体指针
你还可以创建指向结构体的指针,通过指针来访问和修改结构体的成员。
Student *pStudent = &student; // pStudent指向student的地址 | |
printf("Name: %s\n", pStudent->name); // 使用->运算符通过指针访问结构体的成员 |
在C语言中,结构体是一种非常强大的工具,它允许你创建自定义的数据类型,以更好地组织和管理你的代码和数据。
联合类型
在C语言中,联合(Union)是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型。但是,在任何时候,联合中只有一个成员可以有值。联合提供了一种使用相同的内存位置来存储不同数据类型的方式,但一次只能存储其中一种。
联合的定义
联合使用union
关键字进行定义,后面跟着联合的名称和由花括号{}
包围的成员列表。联合的成员可以是任何数据类型,包括基本类型、数组、指针或其他结构体。
下面是一个简单的联合定义示例:
union ExampleUnion { | |
int i; | |
float f; | |
char str[20]; | |
}; |
在这个例子中,ExampleUnion
是一个联合类型,它有三个成员:一个整数i
、一个浮点数f
和一个字符数组str
。但是,在任何时候,这个联合中只有一个成员可以有值。
联合的使用
联合的使用类似于结构体,但是有一个重要的区别:在同一时间,联合只能有一个成员被赋值。这是因为所有成员都在同一块内存位置中。当你给联合的一个成员赋值时,其他成员的值将被覆盖。
int main() { | |
union ExampleUnion u; | |
// 赋值给整数成员 | |
u.i = 10; | |
printf("i: %d\n", u.i); // 输出: i: 10 | |
// 赋值给浮点数成员(这会覆盖整数成员的值) | |
u.f = 220.5; | |
printf("f: %f\n", u.f); // 输出: f: 220.500000 | |
// 尝试访问字符串成员(可能会得到意外的结果,因为这里没有合适的字符串被存储) | |
printf("str: %s\n", u.str); // 输出可能是未定义的,因为str没有被正确初始化或赋值 | |
// 给字符串成员赋值(这会覆盖浮点数成员的值) | |
strcpy(u.str, "Hello"); | |
printf("str: %s\n", u.str); // 输出: str: Hello | |
// 此时,尝试访问整数或浮点数成员将不会得到有意义的结果 | |
// 因为它们的值已经被字符串成员覆盖 | |
return 0; | |
} |
注意事项
- 内存共享:联合的所有成员都共享同一块内存空间。因此,给一个成员赋值会覆盖其他成员的值。
- 大小:联合的大小至少是足够存储其最大成员的大小。但是,具体的内存布局和填充取决于编译器和平台。
- 未初始化:如果在使用联合之前没有给任何成员赋值,那么尝试访问任何成员都可能导致未定义的行为。
- 类型安全:由于联合允许在相同的内存位置存储不同的数据类型,因此在使用联合时要特别小心类型安全。错误地访问或解释联合成员的值可能会导致数据损坏或程序崩溃。
- 用途:联合通常用于需要节省内存空间或需要在同一内存位置存储不同类型数据的场景。但是,由于它们的复杂性和潜在的错误风险,它们通常不如结构体那么常用。
提问环节
问题一:C语言为什么要有这么多数据类型?
答案在下一期揭晓~
------------------------
要求记住各数据类型所占内存大小,关于结构体类型的内存大小怎么计算后期会有专门介绍!