【C语言入门】解锁核心关键字的终极奥秘与实战应用(二)
目录
一、sizeof
1.1. 作用
2.2. 代码示例
二、const
2.1. 作用
2.2. 代码示例
三、signed 和 unsigned
3.1. 作用
3.2. 代码示例
四、struct、union、enum
4.1. struct(结构体)
4.1.1. 作用
4.1.2. 代码示例
4.2. union(联合体)
4.2.1. 作用
4.2.2. 代码示例
4.3. enum(枚举类型)
4.3.1. 作用
4.3.2. 代码示例
五、typedef
5.1. 作用
5.2. 代码示例
接着上一篇【C语言入门】解锁核心关键字的终极奥秘与实战应用(一)-CSDN博客继续分析核心关键字。
一、sizeof
1.1. 作用
-
数据类型:
sizeof
可以直接作用于基本数据类型(如int
,char
,float
,double
等)以及用户定义的数据类型(如结构体、联合体等)。 -
变量:
sizeof
也可以作用于变量,此时它返回的是该变量类型所占的内存大小,而不是变量的值。 -
编译时计算:
sizeof
的计算是在编译时进行的,而不是在运行时。意味着它不会增加程序的运行时间开销。 -
括号:在使用
sizeof
时,通常建议将其操作数放在括号中,以避免潜在的解析歧义。例如,sizeof(int)
而不是sizeof int
。 -
指针:当
sizeof
作用于指针时,它返回的是指针类型本身所占的内存大小,而不是指针所指向的数据的大小。
2.2. 代码示例
示例1:基本数据类型
#include <stdio.h>
int main() {
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of char: %zu bytes\n", sizeof(char));
printf("Size of float: %zu bytes\n", sizeof(float));
printf("Size of double: %zu bytes\n", sizeof(double));
return 0;
}
- 运行结果:
sizeof
被用来计算基本数据类型 int
, char
, float
, 和 double
的大小,并将结果打印出来。
示例2:变量
#include <stdio.h>
int main() {
int a = 10;
char b = 'c';
printf("Size of variable a (int): %zu bytes\n", sizeof(a));
printf("Size of variable b (char): %zu bytes\n", sizeof(b));
// 或者直接使用变量类型
printf("Size of type of variable a: %zu bytes\n", sizeof(int));
printf("Size of type of variable b: %zu bytes\n", sizeof(char));
return 0;
}
- 运行结果:
sizeof
被用来计算变量 a
和 b
的大小,分别是 int
类型和 char
类型。注意,sizeof(a)
和 sizeof(int)
返回的是相同的结果。
示例3:指针
#include <stdio.h>
int main() {
int *ptr = NULL;
printf("Size of pointer: %zu bytes\n", sizeof(ptr));
// 注意:sizeof(*ptr) 将返回 ptr 所指向的 int 类型的大小
printf("Size of type pointed to by ptr: %zu bytes\n", sizeof(*ptr));
return 0;
}
- 运行结果:
sizeof(ptr)
返回的是指针 ptr
本身所占的内存大小,而 sizeof(*ptr)
返回的是 ptr
所指向的 int
类型的大小。
示例4:结构体
#include <stdio.h>
struct MyStruct {
int a;
char b;
double c;
};
int main() {
struct MyStruct s;
printf("Size of struct MyStruct: %zu bytes\n", sizeof(struct MyStruct));
printf("Size of variable s of type struct MyStruct: %zu bytes\n", sizeof(s));
return 0;
}
- 运行结果:
sizeof
被用来计算结构体 MyStruct
的大小。注意,结构体的大小可能会因为内存对齐(padding)而大于其成员大小的总和。
二、const
const
关键字在C/C++等编程语言中用于定义常量,即其值在初始化后不能被修改的变量。使用 const
可以提高代码的可读性和安全性,因为它明确了哪些变量是不应该被修改的。
2.1. 作用
1. 定义常量:const
修饰的变量必须在声明时初始化,之后其值就不能被改变了。
2. 类型安全:通过 const
,编译器可以在编译时检查对常量的非法修改,从而提高代码的类型安全性。
3. 作用域:const
常量的作用域取决于其声明位置。在函数内部声明的 const
常量具有局部作用域,而在文件范围或全局范围内声明的 const
常量则具有相应的全局作用域。
4. 指针与 const
:
const
指针:指向常量的指针,不能通过该指针修改所指向的值。- 指向
const
的指针:指针本身是常量,不能改变其指向的地址,但可以修改所指向的值(如果所指向的不是const
)。 - 指向
const
的const
指针:既不能改变指针的指向,也不能通过指针修改所指向的值。
5. 与 #define
的区别:const
定义的常量有类型,可以进行类型检查;而 #define
定义的常量是简单的文本替换,没有类型信息。
2.2. 代码示例
示例1:基本常量
#include <stdio.h>
int main() {
const int MAX_VALUE = 100; // 定义常量 MAX_VALUE
printf("MAX_VALUE: %d\n", MAX_VALUE);
// MAX_VALUE = 200; // 这将导致编译错误,因为 MAX_VALUE 是常量
return 0;
}
运行结果:
示例2:const
指针
#include <stdio.h>
int main() {
const int a = 5;
int b = 10;
const int *ptr1 = &a; // ptr1 指向常量 a,不能通过 ptr1 修改 a 的值
int *ptr2 = &b; // ptr2 指向变量 b,可以通过 ptr2 修改 b 的值
// *ptr1 = 20; // 这将导致编译错误,因为 ptr1 指向的是常量
*ptr2 = 20; // 这将修改 b 的值为 20
printf("a: %d, b: %d\n", a, b);
return 0;
}
运行结果:
示例3:指向 const
的指针
#include <stdio.h>
int main() {
int a = 5;
const int *ptr = &a; // ptr 指向变量 a,但 ptr 被声明为指向 const,因此不能通过 ptr 修改 a 的值
// ptr = &b; // 假设 int b; 已声明,这将是合法的,但前提是 ptr 没有被声明为指向 const 的 const 指针
// *ptr = 10; // 这将导致编译错误,因为 ptr 指向的值被视为常量
printf("a: %d\n", a);
return 0;
}
运行结果:
示例4:指向 const
的 const
指针
#include <stdio.h>
int main() {
const int a = 5;
const int *const ptr = &a; // ptr 是指向 const 的 const 指针,既不能改变 ptr 的指向,也不能通过 ptr 修改所指向的值
// ptr = &b; // 这将导致编译错误,因为 ptr 是指向 const 的 const 指针
// *ptr = 10; // 这也将导致编译错误,因为 ptr 指向的值被视为常量
printf("a: %d\n", a);
return 0;
}
运行结果:
通过 const
关键字,我们可以定义在程序执行期间其值不应改变的变量,从而提高代码的可读性和健壮性。
三、signed 和 unsigned
在C/C++等编程语言中,signed
和 unsigned
关键字用于定义整数类型的符号性。signed
表示有符号数,可以表示正数、负数和零;而 unsigned
表示无符号数,只能表示非负数(即零和正数)。
3.1. 作用
1. 有符号数(signed):
- 默认情况下,整数类型(如
int
、short
、long
)都是有符号的。 - 有符号数使用最高位作为符号位,0 表示正数,1 表示负数。
- 有符号数的取值范围包括负数、零和正数。
2. 无符号数(unsigned):
- 无符号数不使用符号位,因此可以表示更大的正数范围。
- 无符号数的取值范围从0开始,一直到该类型能表示的最大正数。
- 在声明变量时,可以使用
unsigned
关键字来指定无符号类型,如unsigned int
、unsigned short
、unsigned long
等。
3. 类型转换:
- 当有符号数和无符号数进行运算时,有符号数可能会被隐式转换为无符号数,可能会导致意外的结果。
- 为了避免这种情况,应该显式地进行类型转换,确保运算的正确性。
4. 溢出:
- 当整数超出其类型的取值范围时,会发生溢出。
- 对于有符号数,溢出可能导致结果变为负数或另一个正数。
- 对于无符号数,溢出会导致结果从最大值回绕到0。
3.2. 代码示例
示例1:基本的有符号和无符号整数
#include <stdio.h>
int main() {
signed int a = -10; // 有符号整数,值为-10
unsigned int b = 20; // 无符号整数,值为20
printf("Signed int a: %d\n", a);
printf("Unsigned int b: %u\n", b);
// 有符号和无符号整数相加(注意可能的溢出和类型转换)
int sum_signed = a + b; // 结果为10(有符号运算)
unsigned int sum_unsigned = a + b; // 结果取决于系统,但通常为一个大正数(无符号运算)
printf("Sum (signed): %d\n", sum_signed);
printf("Sum (unsigned): %u\n", sum_unsigned);
return 0;
}
运行结果:
示例2:类型转换和溢出
#include <stdio.h>
#include <limits.h>
int main() {
unsigned int u_max = UINT_MAX; // 无符号整数的最大值
int s_max = INT_MAX; // 有符号整数的最大值
printf("Unsigned int max: %u\n", u_max);
printf("Signed int max: %d\n", s_max);
// 溢出示例
unsigned int u_overflow = u_max + 1; // 结果为0(无符号溢出)
int s_overflow = s_max + 1; // 结果为INT_MIN(有符号溢出)
printf("Unsigned overflow: %u\n", u_overflow);
printf("Signed overflow: %d\n", s_overflow);
// 类型转换示例
unsigned int u = 10;
int s = -5;
// 当有符号数和无符号数进行运算时,有符号数可能会被隐式转换为无符号数
unsigned int result = u + s; // 结果可能不是预期的5,而是一个大正数
printf("Result of u + s (unsigned): %u\n", result);
// 为了避免这种情况,应该显式地进行类型转换
unsigned int result_correct = u + (unsigned int)s; // 仍然可能不是5(因为s是负数),但避免了隐式转换的陷阱
int result_signed = (int)u + s; // 正确的结果为5(因为先将u转换为有符号数,再进行运算)
printf("Corrected result (unsigned to signed): %u\n", result_correct);
printf("Corrected result (signed): %d\n", result_signed);
return 0;
}
运行结果:
在进行有符号和无符号整数的运算时,应该特别小心类型转换和溢出的问题,以避免意外的结果。在实际编程中,应该根据具体的需求选择合适的整数类型,并确保运算的正确性。
四、struct、union、enum
在C/C++等编程语言中,struct
、union
和 enum
是用于定义复合数据类型的关键字。它们允许程序员将多个不同类型的数据组合在一起,或者定义一组命名的整型常量。
4.1. struct(结构体)
struct
关键字用于定义结构体,它是一种用户自定义的数据类型,可以包含多个不同类型的数据成员。结构体通常用于表示具有多个属性的实体,如人、车等。
4.1.1. 作用
- 结构体定义使用
struct
关键字,后跟结构体标签(可选)和大括号内的成员列表。 - 结构体成员可以是任何有效的数据类型,包括基本数据类型、指针、数组、甚至其他结构体。
- 结构体变量可以通过点运算符(
.
)访问其成员。 - 结构体可以嵌套定义,即一个结构体成员可以是另一个结构体类型。
4.1.2. 代码示例
#include <stdio.h>
// 定义一个结构体类型 Person
struct Person {
char name[50];
int age;
float height;
};
int main() {
// 创建一个结构体变量
struct Person person1;
// 给结构体成员赋值
snprintf(person1.name, sizeof(person1.name), "Alice");
person1.age = 30;
person1.height = 5.5;
// 打印结构体成员的值
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Height: %.1f\n", person1.height);
return 0;
}
- 运行结果:
4.2. union(联合体)
union
关键字用于定义联合体,它是一种特殊的数据结构,允许在相同的内存位置存储不同的数据类型。联合体的大小等于其最大成员的大小,且所有成员共享同一块内存。
4.2.1. 作用
- 联合体定义使用
union
关键字,后跟联合体标签(可选)和大括号内的成员列表。 - 联合体成员可以是任何有效的数据类型。
- 联合体变量通过点运算符(
.
)访问其成员,但一次只能存储一个成员的值,因为所有成员共享内存。 - 联合体通常用于节省内存或实现多态。
4.2.2. 代码示例
#include <stdio.h>
// 定义一个联合体类型 Data
union Data {
int i;
float f;
char str[20];
};
int main() {
// 创建一个联合体变量
union Data data;
// 给联合体成员赋值(注意:同时只能有一个成员有效)
data.i = 100;
printf("Integer: %d\n", data.i);
data.f = 3.14;
printf("Float: %.2f\n", data.f);
snprintf(data.str, sizeof(data.str), "Hello");
printf("String: %s\n", data.str);
// 注意:同时访问多个成员可能会导致未定义行为
// 例如:printf("Integer after string: %d\n", data.i); // 未定义行为
return 0;
}
- 运行结果:
4.3. enum(枚举类型)
enum
关键字用于定义枚举类型,它是一种用户定义的类型,由一组命名的整型常量组成。枚举类型使得代码更加清晰易读,并限制了变量的取值范围。
4.3.1. 作用
- 枚举定义使用
enum
关键字,后跟枚举标签(必须)和大括号内的枚举成员列表。 - 枚举成员可以是任何有效的标识符,它们自动被赋予一个整型值,从0开始递增(除非显式指定)。
- 枚举变量可以通过赋值或使用枚举成员来初始化。
- 枚举类型通常用于表示一组相关的常量,如颜色、状态等。
4.3.2. 代码示例
#include <stdio.h>
// 定义一个枚举类型 Color
enum Color {
RED,
GREEN,
BLUE,
YELLOW = 3, // 显式赋值
PURPLE // 自动赋值为4(因为YELLOW=3,所以PURPLE=4)
};
int main() {
// 创建一个枚举变量
enum Color favoriteColor = GREEN;
// 打印枚举变量的值(注意:打印的是整型值)
printf("Favorite color: %d\n", favoriteColor);
// 使用枚举成员进行比较
if (favoriteColor == RED) {
printf("You like red!\n");
} else if (favoriteColor == GREEN) {
printf("You like green!\n");
} else {
printf("You like some other color.\n");
}
return 0;
}
- 运行结果:
在上面的示例中,展示了如何使用 struct
、union
和 enum
来定义复合数据类型,并展示了如何初始化和使用这些类型的变量。这些特性使得C/C++等编程语言非常灵活和强大,能够处理各种复杂的数据结构和常量集合。
五、typedef
typedef
是 C/C++ 语言中的一个关键字,它允许程序员为现有的数据类型定义一个新的名称(别名)。这样做的好处是,它可以使代码更加清晰易读,特别是当处理复杂的数据类型(如结构体、联合体、指针等)时。
5.1. 作用
typedef
的基本语法是typedef existing_type new_type_name;
,其中existing_type
是已经存在的数据类型,new_type_name
是想要定义的新名称。- 使用
typedef
定义的别名,就像使用任何基本数据类型一样,可以用于变量声明、函数参数、返回值类型等。 typedef
经常与结构体(struct
)和联合体(union
)一起使用,以简化对这些复合数据类型的引用。- 还可以为指针类型定义别名,这在处理函数指针和复杂数据结构时特别有用。
5.2. 代码示例
示例1:为结构体定义别名:
#include <stdio.h>
// 定义一个结构体类型
struct Point {
int x;
int y;
};
// 使用 typedef 为结构体类型定义别名
typedef struct Point Point;
int main() {
// 使用别名创建结构体变量
Point p1;
// 给结构体成员赋值
p1.x = 10;
p1.y = 20;
// 打印结构体成员的值
printf("Point p1: (%d, %d)\n", p1.x, p1.y);
return 0;
}
- 运行结果:
typedef struct Point Point;
实际上有点冗余,因为当 struct
标签(Point
)和 typedef
定义的别名相同时,可以直接在 typedef
中定义结构体,如下所示:
typedef struct {
int x;
int y;
} Point;
示例2:为指针类型定义别名:
#include <stdio.h>
// 定义一个函数类型
typedef int (*FuncPtr)(int, int);
// 定义一个函数,该函数符合 FuncPtr 类型的签名
int add(int a, int b) {
return a + b;
}
int main() {
// 使用别名创建函数指针变量
FuncPtr fp = add;
// 通过函数指针调用函数
int result = fp(3, 4);
// 打印结果
printf("Result: %d\n", result);
return 0;
}
- 运行结果: