3.3 杂七杂八补充的东西 | Miscellaneous

发布于 2025-09-29  647 次阅读


杂七杂八补充的东西 | Miscellaneous

一、Switch语句

我们在分支结构这一节,介绍了if-else结构及其嵌套。但我们常常碰到这样一些情况:当一个变量等于某个值A时,执行某段代码;当它等于值B时,执行另一段代码,当它等于值C时,执行又一段代码;...。以此类推。如果我们用此前学的if-else语句来实现,代码大致长这样:

if (var == 1) {
    // 执行1的代码
} else if (var == 2) {
    // 执行2的代码
} else if (var == 3) {
    // 执行3的代码
} else if (var == 4) {
    // 执行4的代码
}

虽然通过if-else结构的嵌套,可以实现这样的功能,但代码多少看起来有点冗长,可读性不强。为了解决这个问题,C语言提供了switch语句。

switch语句是分支结构的一种,它的语法结构如下:

switch (expression) {
    case constant1:
        // 当expression等于constant1时执行的代码
        break;
    case constant2:
        // 当expression等于constant2时执行的代码
        break;
    // 可以有任意数量的case语句
    default:
        // default部分并不是必须存在的。
        // 如果expression不等于任何case中的常量,执行这里的代码
        // 如果没有default语句,而expression又没有任何匹配,则switch语句不会执行任何操作。
}

switch语句的工作原理是:计算expression的值,然后将其与每个case标签后的常量进行比较。如果找到匹配的常量,就执行对应的代码块,直到遇到break语句为止。如果没有任何case匹配,则执行default块中的代码(如果有的话)。

例如,这个输入今天星期几的程序,可以用switch语句来实现:

#include <stdio.h>
int main() {
    int day;
    printf("请输入今天是星期几(1-7):");
    scanf("%d", &day);

    switch (day) {
        case 1:
            printf("今天是星期一\n");
            break;
        case 2:
            printf("今天是星期二\n");
            break;
        case 3:
            printf("今天是星期三\n");
            break;
        case 4:
            printf("今天是星期四\n");
            break;
        case 5:
            printf("今天是星期五\n");
            break;
        case 6:
            printf("今天是星期六\n");
            break;
        case 7:
            printf("今天是星期日\n");
            break;
        default:
            printf("输入无效,请输入1到7之间的数字。\n");
    }

    return 0;
}

二、goto语句

goto语句是一种无条件跳转语句,可以让程序直接跳转到指定的标签位置。我们通过一个简单的例子来说明goto语句的用法:

#include <stdio.h>
int main() {
    printf("这是句子1\n");
    goto skip;      // 跳过下面的代码,直接跳到标签skip处

    printf("这是句子2\n"); // 这行代码将被跳过

skip:               // 标签skip位置
    printf("这是句子3\n");
    return 0;
}

C语言的goto语句是和标签结合起来用的。在这个例子中,程序首先打印"这是句子1",然后遇到goto skip;语句,程序会跳过"这是句子2"的打印,直接跳到标签skip:处,打印"这是句子3"。

值得说明的是,不同于变量使用必须在声明之后,标签可以出现在goto语句之前或之后。这是因为它们在编译时会被编译器解析,而不是像变量声明那样涉及到内存空间的分配。标签的命名规则和变量类似,但通常建议使用有意义的名字以提高代码可读性。

相当重要的是goto语句虽然可以简化某些跳转逻辑,但过度使用会导致代码极度难以理解和维护、增加逻辑错误的风险。此外,goto语句可能会导致无法预料的行为,例如跳过变量声明而直接使用变量等等。因此,建议在编写代码时尽量避免使用goto,除非在某些特定情况下(如错误处理)确实需要它。

这已经成为主流编程规范的共识:

"The goto statement should not be used."(不应使用goto语句。) --MISRA C:2012 Rule 15.1
"Finish every set of statements with a break or return rather than a goto."(应使用breakreturn结束语句块,而不是goto。)--CERT C - MSC17-C

三、常量与const关键字

我们早在1.2时就学习了变量的声明。而与变量相对的是常量。与变量在程序运行过程中,值常常发生改变不同,常量的基本特征是在程序运行时不可改变的量。

常量通常指字面常量常变量,有时也包含宏常量

1.字面常量

字面常量(Literal Constants)是指在代码中直接写出的固定值,比如数字、字符或字符串。它们的值在程序运行期间不会改变。例如:

b = a + 5; // 这里的5就是字面常量。它是在源码中"写好"的,不会改变。

又如:

int a = 100; // a是一个整型变量,而右边的100是常量。
// 这句赋值语句是把常量100的值"赋"给变量a,使得它的值成为100。
// a的值可以再变化,但写好的100就是100。

字面常量也有它们对应数据类型的概念,和变量的类型一一对应。例如常见的字面常量类型有:

  • 整型常量:如 10, -5, 0
  • 浮点型常量:如 3.14, -0.001, 2.0
  • 字符常量:如 'A', 'b', '\n'
  • 字符串常量:如 "Hello", "C语言"

这些常量固定地"写好"在代码中,编译后其值不会改变。

2.常变量

有时,为了避免在多处重复书写某个数值(如圆周率3.1415926),我们会声明一个变量来简化代码,并便于统一修改(例如改为3.14)。

例如:

float pi = 3.1415926;

不难发现,pi这样的值,在程序执行过程中通常应该不被改变。并且要尽量避免因为内存访问错误的原因,被危险地、不知情地修改。为此,仿照字面常量的特征,C语言提供const关键字,可以将一个变量声明为常变量。

常变量的语法格式为:

const 数据类型 变量名 = 初始值;

例如:

const float pi = 3.1415926;
const int max_students = 100;
const char grade = 'A';

const修饰的变量具有以下特点:

  • 必须在声明时初始化
  • 一旦初始化后,值被严格地保护起来,不能被修改
  • 如果试图修改常变量的值,编译器会报错
const int num = 10;
num = 20; // 编译错误:不能修改常变量的值

这样一来,对于一些我们不希望被改变的量,我们可以通过定义常变量将它们更好地“保护起来”。

值得说明的是,常变量与字面常量具有一个重要区别:常变量与变量一样,会在内存中分配一块空间存储它的值,而字面常量则会在编译层面直接处理。

3.宏常量

除了字面常量和常变量,C语言还提供了宏常量的概念。使用#define预处理指令可以定义宏常量:

#define PI 3.1415926
#define MAX_SIZE 1000

宏常量在预处理阶段会被直接替换,相当于文本替换。它们在习惯上,通常使用全大写字母命名以区别于变量。

#include <stdio.h>
#define PI 3.1415926

int main() {
    double radius = 5.0;
    double area = PI * radius * radius;
    printf("圆的面积:%.2f\n", area);
    return 0;
}

由于宏常量文本替换的特性,这样一段啼笑皆非的代码是可以被编译通过的:

#define 喵喵喵 main
#define 喵喵 int
#define 喵 (
#define 喵喵呜 )
#define 喵喵喵喵 {
#define 喵呜喵 }
#define 呜 printf
#define 呜呜 "喵喵喵!"
#define 呜呜呜 return
#define 呜喵 0
#define 喵呜 ;

#include <stdio.h>
喵喵 喵喵喵 喵 喵喵呜
喵喵喵喵
呜 喵 呜呜 喵喵呜 喵呜
呜呜呜 呜喵 喵呜
喵呜喵

运行结果是输出喵喵喵!

由于宏定义”文本替换“的特性,宏常量和字面常量一样,都是在编译层面处理,而不像常变量涉及到内存空间的分配。

三种常量各有千秋,而合理使用常量是良好的编程习惯。

四、三元运算符

三元运算符(Ternary Operator)是C语言中一种简洁的条件表达式,也被称为条件运算符。它是唯一一个需要三个操作数的运算符,语法格式为:

条件表达式 ? 表达式1 : 表达式2

工作原理:

  • 如果条件表达式为真(非零),则整个三元表达式等于表达式1的值
  • 如果条件表达式为假(零),则整个三元表达式等于表达式2的值

例如,以下代码使用三元运算符找出两个数中的最大值:

#include <stdio.h>
int main() {
    int a = 10, b = 20;
    int max = (a > b) ? a : b;
    printf("最大值是:%d\n", max);
    return 0;
}

这等价于使用if-else语句:

int max;
if (a > b) {
    max = a;
} else {
    max = b;
}

三元运算符的优势在于:

  • 简洁性:一行代码就能完成简单的条件判断
  • 表达式性质(重要!):可以直接用在赋值、函数参数等需要表达式的地方

更多应用示例:

// 判断奇偶数
int num = 15;
printf("%d是%s数\n", num, (num % 2 == 0) ? "偶" : "奇");

// 绝对值计算
int x = -5;
int abs_x = (x >= 0) ? x : -x;

// 三元运算符的嵌套(不推荐过度使用)
int score = 85;
char grade = (score >= 90) ? 'A' : (score >= 80) ? 'B' : 'C';

需要注意的是,虽然三元运算符可以使代码更加简洁,但过度使用或嵌套使用会降低代码的可读性。建议在简单的条件判断中使用,复杂的逻辑仍应使用if-else语句。

五、typedef关键字

在C语言中,我们已经熟悉一些常见的数据类型,如intfloatchar等。但从字面上,它们只含有“整数”、“小数”、“字符”的意思。

考虑这样一个例子,我们用一个八位整数来存储某一天的日期。例如2025090120251225等。虽然我们可以直接使用int类型来存储这些值,但从代码可读性的角度来看,直接使用int并不能清晰地表达出这个变量的含义。

为了解决这个问题,我们可以使用typedef关键字为这种特定含义的类型定义一个新的名称。例如:

typedef int Date; // 定义一个新的类型Date,实际上是int的别名

这样,我们就可以用Date来表示日期类型,而不是使用int,即使它本质上还是int。这使得代码更加清晰易懂。

Date today = 20250901;

typedef的语法格式为:

typedef 原有类型 新类型名;

这样,我们就对C语言原有的类型进行了重新命名。typedef并不会创建新的数据类型,它只是为现有类型创建一个新的名字。

typedef有利有弊:

  • 优点
    • 提高代码可读性:通过有意义的类型名,代码更容易理解。
    • 便于维护:如果需要更改底层类型,只需修改typedef定义即可。
    • 简化复杂类型的使用:对于后续要学习的结构体、指针等复杂类型,typedef可以简化声明。
  • 缺点
    • 可能增加理解难度:在代码中突然出现用typedef定义的类型,需要查找其定义,看看到底是什么基本数据类型,增加理解成本。

六、显式与隐式类型转换

在C语言中,类型转换是指将一种数据类型的值转换为另一种数据类型的过程。类型转换分为两种:隐式类型转换显式类型转换

1.隐式类型转换

隐式类型转换(Implicit Type Conversion),也称为自动类型转换,是指编译器在表达式中自动进行的类型转换。当不同类型的数据参与运算时,编译器会根据一定的规则将它们转换为相同的类型,以确保运算的正确性。

例如:

int a = 5;
float b = 2.5;
float c = a + b; // 隐式类型转换,将a中本是int类型的5,在计算时自动转换为float类型5.0,然后与b相加。

又例如:

char ch = 'A'; // 字符'A'的ASCII值是65
int ascii_value = ch; // 隐式类型转换,将char类型的ch转换为int类型65

2.显式类型转换

显式类型转换(Explicit Type Conversion),也称为强制类型转换,是指程序员通过类型转换运算符,明确地将一种数据类型转换为另一种数据类型。显式类型转换的语法格式为:

(目标类型) 表达式

例如:

int a = 10;
int b = 3;
float c = (float)a / (float)b; // 显式类型转换,(float)a和(float)b都看作float类型计算

七、一些系统函数

C语言标准库(头文件)提供了许多有用的系统函数,帮助我们完成各种任务。以下是一些常用的系统函数:

1. 标准输入输出库(stdio.h

在前面的章节中,我们已经多次使用了printfscanf函数,它们都是标准输入输出库中的函数。这个库还包含其他一些有用的函数:

  • getchar(void);:从标准输入读取一个字符
  • putchar(int char);:向标准输出写入一个字符
  • puts(const char *str);:向标准输出写入一个字符串,并在末尾自动添加换行符
  • gets(char *str);:从标准输入读取一行字符串(不推荐使用,存在读取错误的风险),已被C11标准移除。

2. 数学函数库(math.h

数学函数库提供了各种数学运算函数,如:

  • sqrt(double x);:计算平方根
  • pow(double base, double exp);:计算幂
  • sin(double x);:计算正弦值
  • cos(double x);:计算余弦值
  • tan(double x);:计算正切值
  • log(double x);:计算自然对数
  • exp(double x);:计算e的x次幂
  • fabs(double x);:计算绝对值
  • ceil(double x);:向上取整
  • floor(double x);:向下取整
  • round(double x);:四舍五入
  • fmod(double x, double y);:计算x除以y的余数

值得说明的是:

  • 使用这些函数时,需要包含头文件#include <math.h>
  • math.h库中的函数大多以double作为返回类型,特别地,在上面列出的所有函数返回值均是double类型。即使像向上取整、向下取整这样的函数,返回值也是double类型。

3. 时间库(time.h

时间库提供了处理时间和日期的函数,如:

  • time(NULL);:获取当前时间(自1970年1月1日以来的秒数)

4. 标准常用库(stdlib.h

标准常用库提供了一些常用的实用函数,如:

  • abs(int x);:计算整数的绝对值
  • rand();:生成一个随机整数
  • srand(unsigned int seed);:设置随机数种子
  • exit(int status);:终止程序执行,并返回值status给操作系统。类似于在main函数中使用return status;语句。

这里值得说明的是,rand()函数生成的随机数是伪随机数,原理是从一串足够长的“随机”序列中顺序读数。

随机数序列取决于 “种子值”(Seed),如果不加处理,这个值默认为1,也就是每次运行程序时,这个“随机”序列都是相同的。

例如,可能第一次运行一个程序,这个程序调用三次,rand()函数,生成了三个随机数,如312935;第二次运行这个程序,生成的随机数序列没有变,因此调用rand()函数得到的伪随机数序列还是一样为312935、...。

为了获得不同的随机数序列,可以使用srand()函数设置不同的“种子”值。通常,我们会用当前时间来设置种子值,因为时间总是在变化的。

例如:


#include 
#include 
#include 
int main() {
    srand((unsigned int)time(NULL)); // 用当前时间作为随机数种子

    for (int i = 0; i < 5; i++) {
        int random_num = rand(); // 生成随机数
        printf("%d\n", random_num);
    }

    return 0;
}

这里是 /* Huajidawang */ 的个人主页