目录
二、基本数据类型(char、int、float、double等)
三、控制流程语句(if、else、switch、case、default等)
五、控制流程改变语句(break、continue、goto)
C语言的关键字在编程中至关重要,它们定义了程序的基本结构,控制变量的存储、函数的声明与定义,以及程序的流程控制。这些关键字是C语言功能强大的基础,确保了程序的正确性和高效性,是编写C语言程序不可或缺的要素。
一、void
在C语言编程中,void
关键字具有特殊的含义,它表示“无类型”。void
可以用于多种上下文,包括作为函数的返回类型、函数参数的占位符,以及在指针类型中表示通用指针。
1.1. 作用
-
函数返回类型:当
void
用作函数的返回类型时,它表示该函数不返回任何值。这种函数通常用于执行某些操作,如打印输出、修改全局变量或执行某些计算但不返回结果。 -
函数参数占位符:在函数定义中,如果参数列表为
void
(实际上在C语言中函数参数列表为空时通常省略void
,直接写为()
),则表示该函数不接受任何参数。然而,更常见的是,当函数不接受参数时,参数列表被省略为空,而不是显式地写为(void)
。但在某些上下文中,如函数指针类型定义中,使用(void)
来明确指出不接受参数可能更为清晰。 -
通用指针类型:
void *
表示一个指向任意类型的指针。这种指针可以用于存储任何类型的地址,但在使用之前需要进行类型转换。
1.2. 代码示例
1. 函数返回类型为 void
#include <stdio.h>
// 定义一个返回类型为 void 的函数
void printMessage() {
printf("This is a message from a void function.\n");
}
int main() {
printMessage(); // 调用 void 函数
return 0;
}
- 运行结果:
在这个例子中,printMessage
函数不返回任何值,它的作用仅仅是打印一条消息。
2. 函数参数列表为 void
(虽然通常省略):虽然C语言标准中函数不接受参数时参数列表通常省略为空 ()
,但某些情况下为了明确表明不接受参数,可以使用 (void)
。这在函数指针类型定义中更为常见。
#include <stdio.h>
// 定义一个不接受参数的函数(通常省略 void,这里为了说明而显式写出)
void doNothing(void) {
// 不执行任何操作
}
int main() {
doNothing(); // 调用不接受参数的函数
return 0;
}
然而,在实际代码中,doNothing()
的定义更常见地写作 void doNothing()
而不是 void doNothing(void)
,因为前者是C语言的传统写法。
3. void *
作为通用指针类型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义一个使用 void* 的函数,用于复制内存块
void* myMemcpy(void* dest, const void* src, size_t n) {
// 注意:这里只是示例,实际的 memcpy 实现会更为复杂
char* d = (char*)dest;
const char* s = (const char*)src;
while (n--) {
*d++ = *s++;
}
return dest;
}
int main() {
char source[] = "Hello, World!";
char destination[20];
// 使用 myMemcpy 复制字符串
myMemcpy(destination, source, strlen(source) + 1); // 加1是为了包含终止符 '\0'
printf("Destination: %s\n", destination);
return 0;
}
- 运行结果:
在这个例子中,myMemcpy
函数接受 void*
类型的参数,这意味着它可以接受任何类型的指针作为源和目标。然而,在实际使用中,我们需要注意类型转换和内存对齐等问题,并且通常建议使用标准库中的 memcpy
函数来代替自己实现的版本。
二、基本数据类型(char、int、float、double等)
在C语言编程中,char
、int
、float
、double
等是定义变量的基本数据类型,它们各自具有特定的存储大小和取值范围。这些数据类型是编程的基础,用于存储和处理不同类型的数据。
2.1. char(字符类型)
- 存储大小:通常为1字节(8位)。
- 取值范围:在ASCII码表中,取值范围为0到127(或-128到127,取决于编译器和字符集)。在扩展的ASCII码或Unicode中,取值范围可能更大。
- 用途:用于存储单个字符,如字母、数字或符号。
2.2. int(整型)
- 存储大小:通常为2字节(16位)、4字节(32位)或8字节(64位),取决于编译器和平台。
- 取值范围:取决于存储大小和是否有符号(signed)或无符号(unsigned)。例如,32位有符号整型的取值范围为-2,147,483,648到2,147,483,647。
- 用途:用于存储整数。
2.3. float(单精度浮点型)
- 存储大小:通常为4字节(32位)。
- 取值范围:大约为3.4E-38到3.4E+38,精度约为7位十进制数。
- 用途:用于存储小数或需要高精度的数值计算。
2.4. double(双精度浮点型)
- 存储大小:通常为8字节(64位)。
- 取值范围:大约为2.2E-308到1.8E+308,精度约为15位十进制数。
- 用途:用于需要更高精度的数值计算。
2.5. 代码示例
#include <stdio.h>
#include <limits.h> // 包含整型限制值的头文件
#include <float.h> // 包含浮点型限制值的头文件
int main() {
// char 类型示例
char ch = 'A';
printf("char: %c, ASCII value: %d\n", ch, (int)ch);
// int 类型示例
int i = INT_MAX; // 使用 LIMITS_H 中定义的常量
printf("int (max): %d\n", i);
int j = -INT_MAX - 1; // 最小负整数值(对于有符号整型)
printf("int (min): %d\n", j);
// float 类型示例
float f = FLT_MAX; // 使用 FLOAT_H 中定义的常量
printf("float (max): %f\n", f);
float g = 1.23456789f; // 注意:末尾的 f 表示这是一个 float 类型的字面量
printf("float: %.8f\n", g); // 打印8位小数以显示精度
// double 类型示例
double d = DBL_MAX; // 使用 FLOAT_H 中定义的常量
printf("double (max): %lf\n", d);
double e = 1.23456789012345; // double 类型的字面量不需要特殊后缀
printf("double: %.15lf\n", e); // 打印15位小数以显示精度
return 0;
}
在这个示例中,我们定义了不同类型的变量,并使用标准库中的常量(如INT_MAX
和FLT_MAX
)来展示这些类型的取值范围。我们还打印了浮点数的值,并指定了小数点后的位数来展示它们的精度。
实际运行结果:
请注意,实际的取值范围和精度可能因编译器和平台的不同而有所差异。上述代码中的常量(如
INT_MAX
和FLT_MAX
)是通过包含<limits.h>
和<float.h>
头文件来获取的,这些头文件提供了与数据类型限制和属性相关的宏定义。
三、控制流程语句(if、else、switch、case、default等)
在编程中,控制流程语句用于根据条件改变程序的执行路径。C语言提供了几种基本的控制流程语句,包括if
、else
、switch
、case
和default
,它们允许程序根据特定的条件执行不同的代码块。
3.1. if 和 else 语句
if
语句用于根据条件判断执行特定的代码块。如果条件为真(非零),则执行if
块中的代码。else
语句与if
语句配对使用,当if
条件为假(零)时,执行else
块中的代码。- 代码示例:
#include <stdio.h>
int main() {
int number = 10;
if (number > 0) {
printf("The number is positive.\n");
} else {
printf("The number is not positive.\n");
}
return 0;
}
- 实际运行结果:
在这个例子中,因为number
大于0,所以程序会打印“The number is positive.”。
3.2. switch 语句
switch
语句提供了一种更简洁的方式来处理多个条件。它根据一个变量的值选择执行多个代码块中的一个。case
标签用于指定要匹配的值,而default
标签用于处理所有不匹配的情况。- 代码示例:
#include <stdio.h>
int main() {
int day = 3;
switch (day) {
case 1:
printf("Monday\n");
break;
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break;
case 4:
printf("Thursday\n");
break;
case 5:
printf("Friday\n");
break;
case 6:
printf("Saturday\n");
break;
case 7:
printf("Sunday\n");
break;
default:
printf("Invalid day\n");
break;
}
return 0;
}
- 运行结果:
在这个例子中,day
的值是3,所以程序会打印“Wednesday”。注意,每个case
块通常以break
语句结束,以防止“贯穿”(即执行完一个case
块后继续执行下一个case
块的代码)。如果没有break
,则称为“fall-through”,这有时是有用的,但通常是一个错误。
3.3. 注意事项
if
和else
语句可以嵌套使用,以处理更复杂的条件逻辑。- 在
switch
语句中,case
标签必须是常量表达式,并且switch
表达式的类型必须与case
标签的类型兼容。 default
标签是可选的,但如果提供了,它应该是switch
语句中的最后一个标签(尽管在技术上它可以放在任何位置,但放在最后更符合逻辑和可读性)。- 如果
switch
语句中没有break
语句,则程序将继续执行下一个case
块(或default
块,如果存在的话),直到遇到break
或switch
语句的末尾。这种行为称为“贯穿”(fall-through)。
四、循环控制语句(for、while、do-while)
在编程中,循环控制语句允许程序重复执行某段代码,直到满足特定的条件为止。C语言提供了三种基本的循环控制语句:for
、while
和do-while
。
4.1. for 循环
for
循环是最常用的循环结构之一,它通常用于已知循环次数的场景。for
循环的语法结构包括初始化部分、条件判断部分和迭代部分,这些部分都包含在圆括号中,并以分号分隔。- 代码示例:
#include <stdio.h>
int main() {
for (int i = 0; i < 5; i++) {
printf("i = %d\n", i);
}
return 0;
}
在这个例子中,for
循环从i = 0
开始,每次循环迭代后i
的值增加1,直到i
的值达到5为止。因此,循环体内的代码会执行5次,分别打印出i
的值为0到4。
4.2. while 循环
while
循环在每次迭代之前都会检查条件是否为真。如果条件为真,则执行循环体内的代码;如果条件为假,则跳出循环。while
循环通常用于不确定循环次数的场景,但可以通过某种方式在循环体内更新条件以确保最终能够跳出循环。- 代码示例:
#include <stdio.h>
int main() {
int i = 0;
while (i < 5) {
printf("i = %d\n", i);
i++;
}
return 0;
}
- 运行结果:
在这个例子中,while
循环的行为与前面的for
循环相同,都是打印出i
的值为0到4。
4.3. do-while 循环
do-while
循环与while
循环类似,但有一个重要的区别:do-while
循环至少会执行一次,因为条件检查是在循环体的末尾进行的。这意味着,即使条件一开始就是假的,do-while
循环体内的代码也会执行一次。- 代码示例:
#include <stdio.h>
int main() {
int i = 0;
do {
printf("i = %d\n", i);
i++;
} while (i < 5);
return 0;
}
- 运行结果:
在这个例子中,do-while
循环同样会打印出i
的值为0到4。与while
循环不同的是,即使将i
的初始值设置为5(或任何大于或等于5的值),do-while
循环体内的代码仍然会执行一次(尽管在这种情况下,条件判断会立即失败,导致循环不会再次执行)。
4.4. 注意事项
- 在使用循环时,确保循环条件最终能够变为假,以避免无限循环。
- 小心处理循环中的变量更新,确保它们在每次迭代后都按照预期的方式改变。
- 在
for
循环中声明的变量(如上面的int i = 0;
)在循环结束后将不再可用。这是因为它们在循环的初始化部分中声明,并且其作用域仅限于循环体。 - 使用适当的缩进和代码格式来提高循环结构的可读性。
五、控制流程改变语句(break、continue、goto)
在编程中,break
、continue
和goto
语句用于改变程序的正常流程。它们允许程序在特定条件下提前跳出循环、跳过当前迭代或跳转到指定的代码位置。
5.1. break 语句
break
语句用于立即终止最近的循环或switch
语句。当程序执行到break
时,它会跳出当前的循环或switch
块,并继续执行紧随其后的代码。- 代码示例:
#include <stdio.h>
int main() {
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // 当i等于5时,跳出循环
}
printf("i = %d\n", i);
}
printf("Loop exited.\n");
return 0;
}
- 运行结果:
在这个例子中,当i
等于5时,break
语句会终止for
循环,程序会继续执行Loop exited.
的打印语句。
5.2. continue 语句
continue
语句用于跳过当前循环迭代中的剩余部分,并立即开始下一次迭代。它不会终止整个循环,只是跳过当前迭代中continue
之后的代码。- 代码示例:
#include <stdio.h>
int main() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
printf("i = %d\n", i);
}
return 0;
}
- 运行结果:
在这个例子中,continue
语句会跳过所有偶数的打印,因此只有奇数(1, 3, 5, 7, 9)会被打印出来。
5.3. goto 语句
goto
语句允许程序跳转到同一个函数内的指定标签位置。尽管goto
在某些情况下可能很有用(例如,从深层嵌套的循环中跳出),但它通常被认为是一种不良的编程实践,因为它会使代码难以理解和维护。因此,在现代编程中,应尽量避免使用goto
。- 代码示例(尽管不推荐使用,但为了完整性而提供):
#include <stdio.h>
int main() {
int i = 0;
if (i == 0) {
goto skip; // 跳转到标签skip
}
printf("This will not be printed.\n");
skip: // 标签skip
printf("i = %d, jumping here.\n", i);
return 0;
}
- 运行结果:
在这个例子中,由于i
等于0,程序会跳转到标签skip
的位置,并打印出i = 0, jumping here.
。This will not be printed.
语句将不会被执行。
5.4. 注意事项
- 谨慎使用
goto
语句,因为它会破坏代码的结构和可读性。 break
和continue
语句通常用于循环和switch
语句中,以改变它们的正常流程。- 在使用
break
和continue
时,确保它们不会导致逻辑错误或无限循环。
通过合理使用这些控制流程改变语句,可以编写出更加灵活和强大的程序。然而,应始终注意保持代码的可读性和可维护性,避免过度使用这些语句导致代码变得难以理解。
六、return
6.1. 作用
1. 结束函数执行:当执行到return
语句时,函数会立即停止执行,并将控制权返回给调用该函数的地方。
2. 返回值:
- 如果函数返回类型为
void
,则return
语句不需要(也不能)包含返回值。 - 如果函数返回类型不是
void
,则return
语句必须包含一个与该返回类型相匹配的值。
3. 返回值的使用:调用者可以使用从函数返回的值进行进一步的操作。
6.2. 代码示例
示例1:返回类型为void
的函数
#include <stdio.h>
void printMessage() {
printf("This is a message.\n");
return; // 结束函数执行,不需要返回值
}
int main() {
printMessage();
return 0;
}
在这个例子中,printMessage
函数没有返回值(返回类型为void
),因此return
语句后面没有跟任何值。
示例2:返回类型为int
的函数
#include <stdio.h>
int add(int a, int b) {
return a + b; // 返回两个整数的和
}
int main() {
int sum = add(3, 4); // 调用add函数,并将返回值赋给sum
printf("Sum: %d\n", sum); // 输出:Sum: 7
return 0;
}
在这个例子中,add
函数返回两个整数的和,类型为int
。在main
函数中,调用add
函数,并将返回值(即3和4的和)赋给变量sum
。
示例3:没有显式返回值的非void
函数(错误示例)
#include <stdio.h>
int getNumber() {
// 这里缺少return语句,这是一个错误
}
int main() {
int num = getNumber(); // 这将导致未定义行为,因为getNumber没有返回值
printf("Number: %d\n", num);
return 0;
}
在这个错误的例子中,getNumber
函数应该返回一个整数,但实际上没有返回任何值。这将导致未定义行为,因为main
函数中的num
变量将接收一个未初始化的值。
注意:在C/C++等语言中,如果函数声明了返回类型(不是void
),则必须确保在所有可能的执行路径上都有return
语句返回一个值。否则,编译器将报错或产生未定义行为。
七、sizeof
7.1. 作用
-
数据类型:
sizeof
可以直接作用于基本数据类型(如int
,char
,float
,double
等)以及用户定义的数据类型(如结构体、联合体等)。 -
变量:
sizeof
也可以作用于变量,此时它返回的是该变量类型所占的内存大小,而不是变量的值。 -
编译时计算:
sizeof
的计算是在编译时进行的,而不是在运行时。这意味着它不会增加程序的运行时间开销。 -
括号:在使用
sizeof
时,通常建议将其操作数放在括号中,以避免潜在的解析歧义。例如,sizeof(int)
而不是sizeof int
。 -
指针:当
sizeof
作用于指针时,它返回的是指针类型本身所占的内存大小,而不是指针所指向的数据的大小。
7.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
可以提高代码的可读性和安全性,因为它明确了哪些变量是不应该被修改的。
8.1. 作用
1. 定义常量:const
修饰的变量必须在声明时初始化,之后其值就不能被改变了。
2. 类型安全:通过 const
,编译器可以在编译时检查对常量的非法修改,从而提高代码的类型安全性。
3. 作用域:const
常量的作用域取决于其声明位置。在函数内部声明的 const
常量具有局部作用域,而在文件范围或全局范围内声明的 const
常量则具有相应的全局作用域。
4. 指针与 const
:
const
指针:指向常量的指针,不能通过该指针修改所指向的值。- 指向
const
的指针:指针本身是常量,不能改变其指向的地址,但可以修改所指向的值(如果所指向的不是const
)。 - 指向
const
的const
指针:既不能改变指针的指向,也不能通过指针修改所指向的值。
5. 与 #define
的区别:const
定义的常量有类型,可以进行类型检查;而 #define
定义的常量是简单的文本替换,没有类型信息。
8.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
表示无符号数,只能表示非负数(即零和正数)。
9.1. 作用
1. 有符号数(signed):
- 默认情况下,整数类型(如
int
、short
、long
)都是有符号的。 - 有符号数使用最高位作为符号位,0 表示正数,1 表示负数。
- 有符号数的取值范围包括负数、零和正数。
2. 无符号数(unsigned):
- 无符号数不使用符号位,因此可以表示更大的正数范围。
- 无符号数的取值范围从0开始,一直到该类型能表示的最大正数。
- 在声明变量时,可以使用
unsigned
关键字来指定无符号类型,如unsigned int
、unsigned short
、unsigned long
等。
3. 类型转换:
- 当有符号数和无符号数进行运算时,有符号数可能会被隐式转换为无符号数,这可能会导致意外的结果。
- 为了避免这种情况,应该显式地进行类型转换,确保运算的正确性。
4. 溢出:
- 当整数超出其类型的取值范围时,会发生溢出。
- 对于有符号数,溢出可能导致结果变为负数或另一个正数。
- 对于无符号数,溢出会导致结果从最大值回绕到0。
9.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
是用于定义复合数据类型的关键字。它们允许程序员将多个不同类型的数据组合在一起,或者定义一组命名的整型常量。
10.1. struct(结构体)
struct
关键字用于定义结构体,它是一种用户自定义的数据类型,可以包含多个不同类型的数据成员。结构体通常用于表示具有多个属性的实体,如人、车等。
10.1.1. 作用
- 结构体定义使用
struct
关键字,后跟结构体标签(可选)和大括号内的成员列表。 - 结构体成员可以是任何有效的数据类型,包括基本数据类型、指针、数组、甚至其他结构体。
- 结构体变量可以通过点运算符(
.
)访问其成员。 - 结构体可以嵌套定义,即一个结构体成员可以是另一个结构体类型。
10.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;
}
- 运行结果:
10.2. union(联合体)
union
关键字用于定义联合体,它是一种特殊的数据结构,允许在相同的内存位置存储不同的数据类型。联合体的大小等于其最大成员的大小,且所有成员共享同一块内存。
10.2.1. 作用
- 联合体定义使用
union
关键字,后跟联合体标签(可选)和大括号内的成员列表。 - 联合体成员可以是任何有效的数据类型。
- 联合体变量通过点运算符(
.
)访问其成员,但一次只能存储一个成员的值,因为所有成员共享内存。 - 联合体通常用于节省内存或实现多态。
10.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;
}
- 运行结果:
10.3. enum(枚举类型)
enum
关键字用于定义枚举类型,它是一种用户定义的类型,由一组命名的整型常量组成。枚举类型使得代码更加清晰易读,并限制了变量的取值范围。
10.3.1. 作用
- 枚举定义使用
enum
关键字,后跟枚举标签(必须)和大括号内的枚举成员列表。 - 枚举成员可以是任何有效的标识符,它们自动被赋予一个整型值,从0开始递增(除非显式指定)。
- 枚举变量可以通过赋值或使用枚举成员来初始化。
- 枚举类型通常用于表示一组相关的常量,如颜色、状态等。
10.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++ 语言中的一个关键字,它允许程序员为现有的数据类型定义一个新的名称(别名)。这样做的好处是,它可以使代码更加清晰易读,特别是当处理复杂的数据类型(如结构体、联合体、指针等)时。
11.1. 作用
typedef
的基本语法是typedef existing_type new_type_name;
,其中existing_type
是已经存在的数据类型,new_type_name
是想要定义的新名称。- 使用
typedef
定义的别名,就像使用任何基本数据类型一样,可以用于变量声明、函数参数、返回值类型等。 typedef
经常与结构体(struct
)和联合体(union
)一起使用,以简化对这些复合数据类型的引用。- 还可以为指针类型定义别名,这在处理函数指针和复杂数据结构时特别有用。
11.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;
}
- 运行结果:
十二、auto
在C语言中,auto
关键字用于修饰局部变量,尽管在实际编程中,我们往往省略这个关键字,因为局部变量默认就是自动存储类型(auto
)的。auto
关键字的主要作用是显式地表明变量的存储类型,并帮助程序员更好地理解代码。
12.1. 作用
- 修饰局部变量,表明该变量是自动存储类型的。
- 虽然通常省略,但在某些情况下,显式使用
auto
可以提高代码的可读性。
12.2. 特性
- 自动分配内存:当函数被调用时,
auto
变量会在栈上自动分配内存。 - 自动释放内存:当函数返回时,
auto
变量所占用的内存会自动释放,变量失效。 - 生命周期:
auto
变量的生命周期仅限于定义它的函数或代码块内。
12.3. 代码示例
虽然auto
关键字通常被省略,但以下示例可以展示其用法:
#include <stdio.h>
void myFunction() {
auto int myAutoVar = 10; // 显式使用 auto 关键字,但通常可以省略
printf("Value of myAutoVar: %d\n", myAutoVar);
// myAutoVar 在这里有效,但函数返回后将自动释放内存
}
int main() {
myFunction();
// printf("Value of myAutoVar: %d\n", myAutoVar); // 错误:myAutoVar 在这里无效
return 0;
}
在这个示例中,myAutoVar
是一个 auto
类型的局部变量,它在 myFunction
函数内部被定义并初始化。当 myFunction
被调用时,myAutoVar
会在栈上分配内存。当 myFunction
返回时,myAutoVar
所占用的内存会自动释放,变量失效。因此,在 main
函数中尝试访问 myAutoVar
会导致编译错误。
运行结果:
需要注意的是,由于局部变量默认就是
auto
类型的,所以在实际编程中,我们通常会省略auto
关键字。
十三、register
在C语言中,register
关键字用于向编译器提出建议,希望编译器能够将特定的变量存储在CPU的寄存器中,以便提高对该变量的访问速度。寄存器是CPU内部的一种高速存储单元,其访问速度远快于内存。因此,如果某个变量被频繁地读取,且不会被修改,那么将其存储在寄存器中可以显著提高程序的性能。
13.1. 作用
register
关键字的主要作用是向编译器发出建议,希望编译器能够优化变量的存储位置,将其从内存移动到寄存器中。- 然而,需要注意的是,这只是一个建议,编译器并不一定会采纳。编译器会根据自身的优化策略、寄存器的可用性以及变量的使用情况来决定是否将变量存储在寄存器中。
13.2. 特性
-
适用于频繁读取且不会被修改的局部变量:由于寄存器的数量有限,且读写寄存器需要消耗一定的CPU资源,因此
register
关键字最适合用于那些被频繁读取且不会被修改的局部变量。 -
编译器可能会忽略此建议:如前所述,
register
只是一个建议,编译器并不一定会采纳。编译器会根据实际情况来决定是否将变量存储在寄存器中。 -
不能用于全局变量和静态变量:由于全局变量和静态变量在程序的整个生命周期内都有效,且可能会被多个函数访问和修改,因此它们不适合存储在寄存器中。
13.3. 代码示例
下面是一个使用register
关键字的简单示例:
#include <stdio.h>
void count_loops(int n) {
register int i; // 建议编译器将i存储在寄存器中
for (i = 0; i < n; i++) {
// 循环体为空,仅用于演示
}
printf("Loop count: %d\n", i); // 此时i的值应为n
}
int main() {
count_loops(1000000); // 调用函数,传入一个较大的值以演示效果
return 0;
}
在这个示例中,我们使用了register
关键字来建议编译器将循环变量i
存储在寄存器中。然而,需要注意的是,编译器可能会忽略这个建议,并将i
存储在内存中。此外,即使编译器采纳了这个建议,由于寄存器的数量有限,如果程序中使用了大量的register
变量,那么编译器也可能无法将它们全部存储在寄存器中。
运行结果:
因此,在使用
register
关键字时,我们需要保持谨慎,并意识到它只是一个建议,而不是一个强制性的要求。同时,我们还需要通过实际的性能测试来验证编译器是否采纳了我们的建议,并评估其对程序性能的影响。
十四、static
在C语言中,static
关键字有多种用途,包括修饰局部变量、全局变量和函数。
14.1. 修饰局部变量
当static
修饰局部变量时,它会延长该变量的生命周期至整个程序运行期间,但变量的作用域仍然保持不变,即只能在定义它的函数或代码块内部访问。这意味着,即使函数执行完毕,该变量的值也会保留下来,供下次函数调用时使用。
- 代码示例:
#include <stdio.h>
void functionWithStaticVar() {
static int count = 0; // 静态局部变量,只在第一次调用时初始化
count++;
printf("Count: %d\n", count);
}
int main() {
functionWithStaticVar(); // 输出:Count: 1
functionWithStaticVar(); // 输出:Count: 2
functionWithStaticVar(); // 输出:Count: 3
return 0;
}
在上面的示例中,count
是一个静态局部变量。每次调用functionWithStaticVar
函数时,count
的值都会递增,并且在下次函数调用时保留下来。
- 实际运行结果:
14.2. 修饰全局变量
当static
修饰全局变量时,它会限制该变量的作用域,使其只能在定义它的文件内部访问。这有助于避免不同文件之间的命名冲突。
- 代码示例:
// file1.c
#include <stdio.h>
static int globalVar = 100; // 静态全局变量,只能在file1.c内部访问
void printGlobalVar() {
printf("GlobalVar in file1.c: %d\n", globalVar);
}
// file2.c
#include <stdio.h>
// 尝试访问file1.c中的globalVar会导致编译错误
// extern int globalVar; // 注释掉这行以避免编译错误
// void printGlobalVarFromFile2() {
// printf("GlobalVar in file2.c: %d\n", globalVar); // 这行会导致链接错误
// }
int main() {
// printGlobalVarFromFile2(); // 注释掉这行以避免编译错误
printGlobalVar(); // 调用file1.c中的函数来打印globalVar
return 0;
}
在上面的示例中,globalVar
是一个静态全局变量,它只能在file1.c
内部访问。如果尝试在file2.c
中访问它,会导致编译或链接错误。
14.3. 修饰函数
当static
修饰函数时,它会使该函数只能在定义它的文件内部使用,防止外部链接。这有助于隐藏函数的实现细节,减少命名冲突的可能性。
- 代码示例:
// file1.c
#include <stdio.h>
static void internalFunction() {
printf("This is an internal function in file1.c\n");
}
void externalFunction() {
internalFunction(); // 调用内部函数
printf("This is an external function in file1.c\n");
}
// file2.c
#include <stdio.h>
// 尝试调用file1.c中的internalFunction会导致链接错误
// void callInternalFunction() {
// internalFunction(); // 这行会导致链接错误
// }
int main() {
externalFunction(); // 调用file1.c中的外部函数
// callInternalFunction(); // 注释掉这行以避免链接错误
return 0;
}
在上面的示例中,internalFunction
是一个静态函数,它只能在file1.c
内部使用。如果尝试在file2.c
中调用它,会导致链接错误。而externalFunction
是一个外部函数,它可以在其他文件中被调用。
十五、extern
在C语言编程中,extern
关键字扮演着至关重要的角色,它允许我们声明在其他文件中定义的变量或函数,从而实现跨文件的资源共享。这是模块化编程的基础,使得我们可以将程序拆分为多个文件,每个文件负责特定的功能或数据结构,然后在需要时通过 extern
声明来访问这些外部定义的资源。
15.1. 作用
extern
关键字的主要作用是声明一个变量或函数是在其他文件中定义的,这样在当前文件中就可以访问到这个变量或函数。它是实现跨文件链接和访问的关键机制。
15.2. 特性
- 跨文件访问:
extern
允许我们访问在其他文件中定义的变量或函数。 - 声明顺序:在使用
extern
声明的变量或函数之前,编译器需要知道它们的存在。因此,extern
声明通常放在文件的开头部分,或者在变量或函数被实际使用之前。 - 模块化编程:
extern
是模块化编程的基础,它使得我们可以将程序拆分为多个独立的文件,每个文件都可以定义自己的变量和函数,并通过extern
声明来访问其他文件中的资源。
15.3. 代码示例
下面是一个简单的示例,展示了如何使用 extern
关键字来实现跨文件访问变量和函数。
- file1.c
#include <stdio.h>
// 定义一个全局变量
int globalVar = 42;
// 定义一个函数
void printGlobalVar() {
printf("GlobalVar in file1.c: %d\n", globalVar);
}
- file1.h
// 在头文件中使用 extern 来声明 file1.c 中定义的变量和函数
extern int globalVar;
extern void printGlobalVar();
- file2.c
#include <stdio.h>
#include "file1.h" // 包含 file1.c 的头文件以访问其声明的变量和函数
int main() {
// 访问 file1.c 中定义的全局变量
printf("Accessing globalVar from file2.c: %d\n", globalVar);
// 调用 file1.c 中定义的函数
printGlobalVar();
return 0;
}
在这个示例中,file1.c
定义了一个全局变量 globalVar
和一个函数 printGlobalVar()
。然后,在 file1.h
中使用 extern
关键字来声明这些变量和函数,以便在其他文件中访问它们。最后,在 file2.c
中,我们包含了 file1.h
头文件,从而能够访问 file1.c
中定义的变量和函数。
通过这种方式,我们可以将程序拆分为多个文件,每个文件负责特定的功能或数据结构,然后通过 extern
声明和头文件来实现跨文件的资源共享和访问。这是模块化编程的核心思想之一。
十六、volatile
volatile
关键字在C/C++等编程语言中用于告诉编译器,某个变量的值可能会在程序的控制流之外被改变。这通常发生在硬件访问、多线程编程或中断服务程序中。使用 volatile
可以防止编译器对该变量进行优化,从而确保每次访问该变量时都能读取其最新的值。
16.1. 作用
-
防止优化:编译器在优化代码时,可能会将变量的值缓存在寄存器中,以减少对内存的访问。如果变量被声明为
volatile
,编译器就不会进行这种优化,而是每次访问该变量时都直接从内存中读取其值。 -
硬件访问:在嵌入式系统编程中,
volatile
常用于访问硬件寄存器的值。这些寄存器的值可能会由硬件本身或其他外部设备改变,因此需要使用volatile
来确保每次都能读取到最新的值。 -
多线程编程:在多线程环境中,一个线程可能会修改另一个线程中的变量。虽然C/C++标准并没有将
volatile
定义为线程之间的同步机制,但在某些平台上,使用volatile
可以防止编译器对共享变量的优化,从而增加线程间通信的可靠性(尽管这不是跨平台或标准的方法,通常应使用同步原语如互斥锁)。 -
中断服务程序:在中断服务程序中,全局变量的值可能会由中断处理程序改变。使用
volatile
可以确保主程序在访问这些变量时能够读取到最新的值。
16.2. 代码示例
示例1:硬件寄存器访问
#include <stdint.h>
// 假设有一个硬件寄存器的地址是0x40000000
#define HARDWARE_REGISTER *((volatile uint32_t *)0x40000000)
int main() {
// 读取硬件寄存器的值
uint32_t value = HARDWARE_REGISTER;
// 对寄存器进行写操作
HARDWARE_REGISTER = 0xDEADBEEF;
// 再次读取寄存器的值
value = HARDWARE_REGISTER;
return 0;
}
在这个例子中,HARDWARE_REGISTER
是一个宏,它定义了一个指向硬件寄存器地址的 volatile
指针。这确保了每次访问 HARDWARE_REGISTER
时都会直接从硬件寄存器中读取或写入值。
示例2:多线程编程中的共享变量
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 注意:这不是跨平台或标准的方法来实现线程间同步
volatile int shared_variable = 0;
void *thread_function(void *arg) {
// 模拟一些工作
sleep(1);
// 修改共享变量的值
shared_variable = 1;
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
// 等待线程完成工作
while (shared_variable == 0) {
// 这里可能会进行忙等待,但这不是推荐的做法
// 在实际应用中,应该使用条件变量、信号量等同步原语
}
printf("Shared variable has been changed by the thread.\n");
pthread_join(thread, NULL);
return 0;
}
在这个例子中,shared_variable
是一个 volatile
变量,它在多线程环境中被共享。虽然 volatile
在这里可以防止编译器对 shared_variable
的优化,但它并不能保证线程之间的同步。在实际应用中,应该使用互斥锁、条件变量等同步原语来确保线程之间的正确同步。
需要注意的是,
volatile
并不提供原子性保证,即它不能保证对volatile
变量的读写操作是原子的。在多线程环境中,即使使用了volatile
,也可能需要额外的同步机制来确保对共享变量的正确访问。
十七、总结
C语言共有32个关键字。这些关键字根据作用可以分为以下四类:
- 数据类型关键字(12个):用于声明变量的数据类型。包括char、double、float、int、long、short、signed、unsigned、struct、union、enum、void等。
- 控制语句关键字(12个):用于控制程序的流程。包括for、do、while、break、continue、if、else、goto、switch、case、default、return等。
- 存储类型关键字(4个或5个):用于声明变量的存储类型。常见的包括auto、extern、static、register等,有时也将typedef视为存储类型关键字的一种,尽管其主要功能是为数据类型取别名。
- 其他关键字(3个或4个):包括const(声明只读变量)、sizeof(计算数据类型长度)、volatile(说明变量在程序执行中可被隐含地改变),有时不包括typedef,因其主要功能是数据类型定义。
这些关键字在C语言编程中具有重要的作用,是构成C语言程序的基本元素之一。掌握这些关键字的使用方法和注意事项,对于学习C语言和进行C语言编程至关重要。
标签:return,变量,示例,int,C语言,关键字,详解,printf,include From: https://blog.csdn.net/weixin_37800531/article/details/142887050