C语言基础14课 第二课

4 阅读29分钟

第2章 变量与数据类型

2.1 数据类型概述

在C语言中,数据类型用于定义变量可以存储的数据类型、变量占用的内存空间大小以及变量的取值范围。数据类型是C语言的基础,正确理解和使用数据类型对于编写高效、安全的C程序至关重要。

2.1.1 数据类型的分类

C语言的数据类型可以分为以下几类:

  • 基本数据类型

    • 整型:用于表示整数
    • 浮点型:用于表示带有小数部分的数值
    • 字符型:用于表示单个字符
  • 构造数据类型:由基本数据类型组合而成

    • 数组:相同类型数据的集合
    • 结构体:不同类型数据的集合
    • 联合体:可以存储不同类型数据的特殊结构
    • 枚举:一组命名的整数常量
  • 指针类型:用于存储内存地址,可以指向任何数据类型

  • 空类型:表示没有值的数据类型,用void表示,主要用于函数返回值和指针类型

2.1.2 数据类型的重要性

  • 内存管理:不同的数据类型占用不同大小的内存空间,合理选择数据类型可以节省内存
  • 性能优化:合适的数据类型可以提高程序的执行效率
  • 类型安全:编译器可以根据数据类型进行类型检查,减少编程错误
  • 代码可读性:明确的数据类型可以提高代码的可读性和可维护性

章节趣事:布尔类型的引入

C语言中的布尔类型(bool)是在C99标准中才引入的,在此之前,C语言没有专门的布尔类型,通常使用int类型来表示布尔值,0表示false,非0表示true。

2.2 基本数据类型

2.2.1 整型

整型用于表示整数,包括正整数、负整数和零。C语言提供了多种整型,它们的区别在于占用的内存空间大小和表示的数值范围不同。

1. 整型的分类

C语言中的整型可以分为不同的类型,主要区别在于内存大小和取值范围:

数据类型关键字内存大小(典型值)最小值最大值格式化字符
短整型short 或 short int2字节-3276832767%hd
整型int4字节-21474836482147483647%d
长整型long 或 long int4字节(32位系统)/ 8字节(64位系统)-21474836482147483647(32位)/ 9223372036854775807(64位)%ld
长长整型long long 或 long long int8字节-92233720368547758089223372036854775807%lld
2. 有符号与无符号整型

C语言中的整型默认是有符号的,可以使用signed关键字显式声明,也可以使用unsigned关键字声明为无符号整型。

  • 有符号整型:可以表示正整数、负整数和零,最高位为符号位(0表示正数,1表示负数)
  • 无符号整型:只能表示非负数,所有位都用于存储数值,没有符号位
数据类型关键字内存大小数值范围格式化字符
无符号短整型unsigned short 或 unsigned short int2字节0 到 65535%hu
无符号整型unsigned int4字节0 到 4294967295%u
无符号长整型unsigned long 或 unsigned long int4字节(32位)/ 8字节(64位)0 到 4294967295(32位)/ 18446744073709551615(64位)%lu
无符号长长整型unsigned long long 或 unsigned long long int8字节0 到 18446744073709551615%llu
3. 整型的存储方式

整型在计算机中以二进制形式存储,有符号整型使用补码表示法:

  • 正数:原码、反码、补码相同

  • 负数

    • 原码:最高位为1,其余位为数值的二进制表示
    • 反码:原码除符号位外,其余位取反
    • 补码:反码加1
  • :只有一种表示方式,全0

示例:整型存储

以8位有符号整型为例:

  • 十进制5的补码:00000101
  • 十进制-5的补码:11111011(原码10000101 → 反码11111010 → 补码11111011)
4. 不同编译器下的整型大小差异

C标准只规定了整型的最小大小和大小关系,具体大小可能因编译器和操作系统而异:

系统/编译器shortintlonglong long
32位Windows (MSVC)2字节4字节4字节8字节
32位Linux (GCC)2字节4字节4字节8字节
64位Windows (MSVC)2字节4字节4字节8字节
64位Linux (GCC)2字节4字节8字节8字节
64位macOS (Clang)2字节4字节8字节8字节
5. 整型常量

整型常量可以用不同的进制表示:

  • 十进制:默认表示,如100-20
  • 八进制:以0开头,如0123(十进制83)
  • 十六进制:以0x或0X开头,如0x1A(十进制26)、0XFF(十进制255)

可以使用后缀指定整型常量的类型:

  • l 或 L:长整型,如100L
  • ll 或 LL:长长整型,如100LL
  • u 或 U:无符号整型,如100U
  • 组合后缀:如100UL(无符号长整型)
6. 整型溢出问题

当整型变量的值超过其类型的最大值或低于最小值时,会发生溢出。溢出是未定义行为,结果不可预测。

示例:整型溢出
#include <stdio.h>

int main() {
    unsigned char uc = 255;
    signed char sc = 127;
    
    printf("unsigned char 255 + 1 = %u\n", uc + 1);  // 溢出,结果为0
    printf("signed char 127 + 1 = %d\n", sc + 1);    // 溢出,结果为-128
    
    return 0;
}
注意事项
  • 始终选择合适大小的整型,避免溢出
  • 对于无符号整型,使用unsigned关键字明确表示
  • 当需要存储大整数时,使用long long类型
  • 避免依赖特定编译器下的整型大小,使用sizeof运算符检查

疑难解析:整型溢出问题

当整型变量的值超过其类型的最大值或低于最小值时,会发生溢出。溢出是未定义行为,结果不可预测。例如,对于signed char类型,最大值是127,当执行char c = 127 + 1;时,结果可能是-128,这是因为补码表示的溢出行为。

易错部分:整型使用误区

  • 混用有符号和无符号整型:当有符号整型和无符号整型进行运算时,有符号整型会被转换为无符号整型,可能导致意外结果。
  • 忽略整型常量的类型:例如,1000000000可能超出int类型的范围,应使用1000000000LL明确指定为长长整型。
  • 使用错误的格式化字符:例如,使用%d格式化无符号整型,可能导致输出错误。

2.2.2 浮点型

浮点型用于表示带有小数部分的数值,也可以表示整数。C语言提供了多种浮点型,它们的区别在于内存大小、精度和数值范围。

1. 浮点型的分类
数据类型关键字内存大小精度数值范围格式化字符
单精度浮点型float4字节约6-7位有效数字1.2E-38 到 3.4E38%f, %e, %g
双精度浮点型double8字节约15-17位有效数字2.2E-308 到 1.8E308%lf, %le, %lg
长双精度浮点型long double8字节/12字节/16字节约18-31位有效数字3.3E-4932 到 1.1E4932%Lf, %Le, %Lg
2. 浮点型的存储方式

浮点型在计算机中按照IEEE 754标准存储,分为三部分:

  • 符号位(1位) :0表示正数,1表示负数

  • 指数位:用于表示科学计数法中的指数

    • float:8位,偏移量127
    • double:11位,偏移量1023
  • 尾数位:用于表示科学计数法中的尾数

    • float:23位,隐含1位整数部分
    • double:52位,隐含1位整数部分
示例:浮点型存储

float类型的1.5为例:

  • 二进制表示:1.1 × 2^0
  • 符号位:0(正数)
  • 指数位:0 + 127 = 127(二进制:01111111)
  • 尾数位:1(二进制:10000000000000000000000)
  • 完整二进制:0 01111111 10000000000000000000000
3. 浮点型的精度问题

浮点型是近似表示,存在精度限制。例如,某些十进制小数无法用二进制精确表示,会产生精度误差。

示例:浮点精度问题
#include <stdio.h>

int main() {
    float f = 0.1;
    double d = 0.1;
    
    printf("float 0.1 = %.20f\n", f);    // 输出:0.10000000149011611938
    printf("double 0.1 = %.20f\n", d);   // 输出:0.10000000000000000555
    
    // 比较两个浮点数是否相等的正确方式
    float a = 0.1 + 0.2;
    float b = 0.3;
    if (fabs(a - b) < 1e-6) {
        printf("a 和 b 相等\n");
    } else {
        printf("a 和 b 不相等\n");
        printf("a = %.20f\n", a);  // 0.30000001192092895508
        printf("b = %.20f\n", b);  // 0.29999998211860656739
    }
    
    return 0;
}
4. 浮点型常量

浮点型常量可以用两种形式表示:

  • 十进制形式:必须包含小数点,如3.140.5.55.
  • 科学计数法形式:使用指数表示,如3.14e01.23e-45.67E+8

浮点型常量默认是double类型,可以使用后缀指定类型:

  • f 或 F:单精度浮点型,如3.14f
  • l 或 L:长双精度浮点型,如3.14L
5. 浮点型的特殊值

根据IEEE 754标准,浮点型有几个特殊值:

  • 正无穷大+infinity,如1.0 / 0.0
  • 负无穷大-infinity,如-1.0 / 0.0
  • NaN(Not a Number) :表示无效数值,如0.0 / 0.0sqrt(-1.0)
示例:浮点型特殊值
#include <stdio.h>
#include <math.h>

int main() {
    float inf = 1.0f / 0.0f;
    float neg_inf = -1.0f / 0.0f;
    float nan = 0.0f / 0.0f;
    
    printf("inf = %f\n", inf);        // inf
    printf("neg_inf = %f\n", neg_inf);  // -inf
    printf("nan = %f\n", nan);          // nan
    
    return 0;
}
注意事项
  • 避免直接比较两个浮点数是否相等,应比较它们的差值是否小于一个很小的数(如1e-6)
  • 优先使用double类型,它具有更高的精度
  • 了解浮点型的精度限制,避免在需要精确计算的场景中使用(如金融计算)
  • 使用float类型时,注意添加f后缀,否则会被视为double类型

疑难解析:为什么浮点数不能精确表示某些小数?

这是因为浮点数在计算机中以二进制形式存储,而某些十进制小数无法用有限的二进制小数精确表示。例如,十进制的0.1转换为二进制是一个无限循环小数:0.0001100110011...,因此无法用有限的二进制位精确表示,只能近似表示。

易错部分:浮点型使用误区

  • 直接比较两个浮点数是否相等:由于精度问题,两个理论上相等的浮点数可能有微小差异,直接使用==比较可能返回false。
  • 使用浮点数作为循环计数器:由于精度累积误差,可能导致循环次数不符合预期。
  • 忽略浮点常量的默认类型:浮点常量默认是double类型,将其赋值给float变量时可能会有警告。

2.2.3 字符型

字符型用于表示单个字符,用char关键字表示,通常占用1字节内存。字符型实际上存储的是字符的ASCII码值,因此可以当作整数使用。

1. 字符型的分类
  • char:默认类型,具体是有符号还是无符号取决于编译器

    • 大多数编译器中,char默认为signed char
    • 但在某些编译器(如ARM)中,char默认为unsigned char
  • signed char:有符号字符型,范围为-128到127

  • unsigned char:无符号字符型,范围为0到255

2. ASCII码表

ASCII(American Standard Code for Information Interchange)是美国信息交换标准代码,定义了128个字符的编码。

ASCII码字符描述
0-31不可打印字符控制字符,如换行符、制表符等
32空格空格字符
48-570-9数字字符
65-90A-Z大写字母
97-122a-z小写字母
127DEL删除字符
示例:字符与ASCII码的转换
#include <stdio.h>

int main() {
    char ch1 = 'A';
    char ch2 = 66;
    
    printf("字符 'A' 的ASCII码:%d\n", ch1);  // 输出:65
    printf("ASCII码 66 对应的字符:%c\n", ch2);  // 输出:B
    
    // 字符型可以进行算术运算
    char ch3 = 'A' + 32;  // 大写字母转小写字母
    printf("'A' + 32 = %c\n", ch3);  // 输出:a
    
    return 0;
}
3. 转义字符

转义字符用于表示一些特殊字符,以反斜杠``开头:

转义字符描述
\n换行符
\t水平制表符
\r回车符
\b退格符
\f换页符
\a响铃符
\反斜杠字符
'单引号字符
"双引号字符
\0空字符(ASCII码为0)
\xhh十六进制表示的字符,hh为两位十六进制数
\ddd八进制表示的字符,ddd为1-3位八进制数
示例:转义字符的使用
#include <stdio.h>

int main() {
    printf("Hello\nWorld!\n");  // 换行
    printf("Name\tAge\tGender\n");  // 制表符
    printf(""C Language"\n");  // 输出双引号
    printf("\path\to\file\n");  // 输出反斜杠
    printf("\x41\x42\x43\n");  // 十六进制转义,输出ABC
    printf("\101\102\103\n");  // 八进制转义,输出ABC
    
    return 0;
}
4. 宽字符与多字节字符

除了基本的char类型,C语言还支持宽字符和多字节字符,用于表示非ASCII字符(如中文、日文等):

  • wchar_t:宽字符类型,用于表示Unicode字符,需要包含头文件<wchar.h>

    • 通常占用2字节或4字节内存
    • 使用L前缀表示宽字符常量,如L'中'
    • 使用%lc格式化输出宽字符
  • C11标准新增了char16_tchar32_t类型:

    • char16_t:16位Unicode字符(UTF-16编码)
    • char32_t:32位Unicode字符(UTF-32编码)

章节趣事:字符型的本质

在C语言中,字符类型(char)实际上是一种整数类型,它占用1字节内存,可以表示ASCII码表中的字符。例如,字符'A'的ASCII码值是65,因此char ch = 'A';等价于char ch = 65;

2.2.4 布尔类型

C99标准引入了布尔类型,用于表示真和假。布尔类型的引入使代码更加清晰易读。

1. 布尔类型的使用
  • 需要包含头文件<stdbool.h>
  • 布尔类型的关键字是bool
  • 布尔常量:true(表示真,值为1)和false(表示假,值为0)
  • 布尔变量的大小通常为1字节
示例:布尔类型的使用
#include <stdio.h>
#include <stdbool.h>

int main() {
    bool is_student = true;
    bool is_adult = false;
    
    printf("is_student = %d\n", is_student);  // 输出:1
    printf("is_adult = %d\n", is_adult);      // 输出:0
    
    // 布尔类型可以用于条件判断
    if (is_student) {
        printf("你是学生\n");
    } else {
        printf("你不是学生\n");
    }
    
    // 布尔类型可以进行逻辑运算
    bool result = is_student && !is_adult;
    printf("result = %d\n", result);  // 输出:1
    
    return 0;
}
2. 布尔类型的实现

在头文件<stdbool.h>中,布尔类型是通过宏定义实现的:

#define bool _Bool
#define true 1
#define false 0
#define __bool_true_false_are_defined 1

其中_Bool是C99标准引入的关键字,是真正的布尔类型。

章节练习:基本数据类型

  1. 编写程序,输出各种基本数据类型的大小、最小值和最大值。
  2. 编写程序,演示有符号整型和无符号整型的区别。
  3. 编写程序,验证浮点数的精度问题。
  4. 编写程序,演示字符型和整型的转换。
  5. 编写程序,使用布尔类型进行条件判断。

2.3 变量

变量是程序中用于存储数据的容器,它具有名称、类型和值。在C语言中,使用变量之前必须先声明,声明时需要指定变量的数据类型。

2.3.1 变量的声明与定义

在C语言中,变量的声明和定义是两个不同的概念:

  • 变量声明:告诉编译器变量的名称和类型,但不分配内存空间

    • 使用extern关键字声明外部变量
    • 例如:extern int count;
  • 变量定义:告诉编译器变量的名称、类型和初始值(可选),并分配内存空间

    • 例如:int count = 10;

变量声明的语法格式:

数据类型 变量名;

例如:

int age;          // 声明一个整型变量age
float height;     // 声明一个浮点型变量height
char grade;       // 声明一个字符型变量grade
bool is_student;  // 声明一个布尔型变量is_student

可以在同一行声明多个同类型的变量:

int a, b, c;      // 声明三个整型变量ab、c
float x, y;       // 声明两个浮点型变量x、y

2.3.2 变量的初始化

变量初始化是在声明变量的同时给它赋初始值。初始化可以确保变量在使用前有一个确定的值,避免使用未初始化的变量导致的未定义行为。

1. 初始化的方式
  • 直接初始化:在声明时直接赋值

    int age = 18;                // 初始化整型变量age为18
    float height = 1.75;         // 初始化浮点型变量height为1.75
    char grade = 'A';            // 初始化字符型变量grade为'A'
    bool is_student = true;      // 初始化布尔型变量is_student为true
    
  • 赋值初始化:先声明后赋值

    int age;                     // 声明变量age
    age = 18;                     // 赋值初始化
    
  • 列表初始化(C99+) :使用大括号进行初始化

    int a = {10};                // C99+列表初始化
    int b = {};                  // 初始化为0
    int c[] = {1, 2, 3};         // 数组列表初始化
    
2. 未初始化变量的问题

未初始化的局部变量会包含随机值(垃圾值),使用未初始化的变量是未定义行为,结果不可预测。

示例:未初始化变量的问题
#include <stdio.h>

int main() {
    int x;  // 未初始化的局部变量
    printf("x = %d\n", x);  // 输出随机值,可能每次运行结果不同
    
    return 0;
}

2.3.3 变量的命名规则

变量名必须遵循以下规则:

  • 只能包含字母(a-zA-Z)、数字(0-9)和下划线(_)
  • 不能以数字开头
  • 区分大小写(age和Age是不同的变量)
  • 不能使用C语言的关键字(如int、float、char等)
  • 不能使用C语言的保留标识符(如__func__、__LINE__等)
1. 命名规范

良好的命名规范可以提高代码的可读性和可维护性:

  • 驼峰命名法:第一个单词小写,后续单词首字母大写,如studentCountaverageScore
  • 下划线命名法:单词之间用下划线连接,如student_countaverage_score(C语言常用)
  • 匈牙利命名法:变量名前加类型前缀,如iCountfScore(不推荐,现代C语言编程中很少使用)
2. 命名示例
类型好的命名示例不好的命名示例
整型student_countagea123abc
浮点型average_scoretemperaturetempf
字符型first_name_initialgradecchar
布尔型is_studenthas_permissionflagbool

2.3.4 变量的作用域与生命周期

变量的作用域是指变量可以被访问的代码范围,生命周期是指变量在内存中存在的时间。

1. 作用域的分类
  • 局部作用域:在函数内部或复合语句内部声明的变量,只能在该函数或复合语句内部访问

    void func() {
        int x = 10;  // 局部变量,作用域在func函数内部
        if (x > 5) {
            int y = 20;  // 局部变量,作用域在if语句块内部
            printf("x = %d, y = %d\n", x, y);  // 可以访问xy
        }
        printf("x = %d\n", x);  // 可以访问x
        // printf("y = %d\n", y);  // 错误,无法访问y
    }
    
  • 全局作用域:在所有函数外部声明的变量,可以在整个程序中访问

    int global_var = 100;  // 全局变量,作用域在整个程序
    
    void func1() {
        printf("global_var = %d\n", global_var);  // 可以访问全局变量
    }
    
    void func2() {
        printf("global_var = %d\n", global_var);  // 可以访问全局变量
    }
    
  • 函数原型作用域:在函数原型中声明的参数,作用域仅限于该函数原型

    void func(int x);  // x的作用域仅限于此函数原型
    
  • 文件作用域:使用static关键字声明的全局变量,作用域仅限于当前文件

    static int file_var = 50;  // 文件作用域变量,只能在当前文件中访问
    
2. 存储类别

变量的存储类别决定了变量的存储位置和生命周期:

存储类别关键字存储位置生命周期作用域初始化
自动存储auto(默认)函数调用期间局部随机值(未初始化)
静态存储static数据段整个程序运行期间局部或文件0(未初始化)
寄存器存储register寄存器(或栈)函数调用期间局部随机值(未初始化)
外部存储extern数据段整个程序运行期间全局0(未初始化)
3. 静态变量的特点

static关键字可以用于局部变量和全局变量:

  • 静态局部变量

    • 存储在数据段,生命周期为整个程序运行期间
    • 只初始化一次,多次调用函数时保持上次的值
    • 作用域仍然是局部的,只能在声明它的函数内部访问
  • 静态全局变量

    • 存储在数据段,生命周期为整个程序运行期间
    • 作用域仅限于当前文件,不能被其他文件访问
    • 避免了全局变量的命名冲突
示例:静态局部变量
#include <stdio.h>

void count() {
    static int cnt = 0;  // 静态局部变量,只初始化一次
    cnt++;
    printf("cnt = %d\n", cnt);
}

int main() {
    count();  // 输出:cnt = 1
    count();  // 输出:cnt = 2
    count();  // 输出:cnt = 3
    
    return 0;
}

2.3.5 变量的使用最佳实践

  • 始终初始化变量,避免使用未初始化的变量

  • 选择合适的数据类型,避免内存浪费和溢出问题

  • 使用有意义的变量名,提高代码可读性

  • 尽量使用局部变量,减少全局变量的使用

    • 全局变量会占用整个程序运行期间的内存
    • 全局变量容易导致命名冲突
    • 全局变量会使函数之间产生耦合,不利于代码维护
  • 避免变量作用域过大,尽量缩小变量的作用域

  • 合理使用静态变量,只在需要时使用

  • 不要使用register关键字,现代编译器会自动优化寄存器分配

疑难解析:局部变量和全局变量的区别

局部变量和全局变量的主要区别在于作用域和生命周期:

  • 作用域:局部变量只在声明它的函数或语句块内有效;全局变量在整个程序中有效。
  • 生命周期:局部变量在函数调用时创建,函数返回时销毁;全局变量在程序启动时创建,程序结束时销毁。
  • 存储位置:局部变量通常存储在栈上;全局变量存储在数据段。
  • 初始化:局部变量如果未初始化,其值是随机的;全局变量如果未初始化,会被自动初始化为0。

易错部分:变量使用误区

  • 使用未初始化的局部变量:未初始化的局部变量值是随机的,使用它会导致未定义行为。
  • 变量名与关键字或函数名冲突:例如,使用int float;int printf;会导致编译错误。
  • 全局变量命名冲突:不同文件中的全局变量如果同名,会导致链接错误。
  • 变量作用域过大:变量作用域过大容易导致意外修改,降低代码的可维护性。

2.3.6 变量的使用示例

#include <stdio.h>

// 全局变量
int global_var = 100;

void func() {
    // 局部变量
    int local_var = 200;
    
    // 访问全局变量和局部变量
    printf("global_var = %d\n", global_var);
    printf("local_var = %d\n", local_var);
    
    // 修改全局变量
    global_var = 300;
}

int main() {
    // 访问全局变量
    printf("初始 global_var = %d\n", global_var);
    
    // 调用函数
    func();
    
    // 查看修改后的全局变量
    printf("修改后 global_var = %d\n", global_var);
    
    // 局部变量
    int a = 10;
    int b = 20;
    int sum = a + b;
    
    printf("a = %d, b = %d, sum = %d\n", a, b, sum);
    
    return 0;
}

运行结果:

初始 global_var = 100
global_var = 100
local_var = 200
修改后 global_var = 300
a = 10, b = 20, sum = 30

2.4 sizeof运算符

sizeof是C语言的一个运算符,用于获取数据类型或变量占用的内存字节数。其语法格式:

sizeof(数据类型) 或 sizeof(变量名)

例如:

#include <stdio.h>

int main() {
    printf("sizeof(char) = %d\n", sizeof(char));
    printf("sizeof(short) = %d\n", sizeof(short));
    printf("sizeof(int) = %d\n", sizeof(int));
    printf("sizeof(long) = %d\n", sizeof(long));
    printf("sizeof(long long) = %d\n", sizeof(long long));
    printf("sizeof(float) = %d\n", sizeof(float));
    printf("sizeof(double) = %d\n", sizeof(double));
    
    int a = 10;
    printf("sizeof(a) = %d\n", sizeof(a));
    
    return 0;
}

运行结果(不同编译器可能有所不同):

sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 4
sizeof(long long) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(a) = 4

章节趣事:sizeof运算符的特殊性

sizeof运算符是C语言中唯一的一个以关键字形式出现的运算符,它不是函数。因此,在计算变量的大小时,可以省略括号,例如sizeof a等价于sizeof(a)

易错部分:sizeof运算符使用误区

  • 将sizeof用于表达式时:sizeof运算符在编译时求值,因此sizeof(i++)不会导致i的值增加。
  • 混淆sizeof返回值的类型:sizeof运算符返回的是size_t类型,而不是int类型,在printf中应该使用%zu格式化输出。
  • 错误计算数组元素个数:对于数组int arr[10];sizeof(arr)返回的是整个数组的大小,而不是元素个数,元素个数应该是sizeof(arr) / sizeof(arr[0])

2.5 常量

常量是程序中固定不变的值,分为字面常量和符号常量。

2.5.1 字面常量

字面常量是直接出现在程序中的值,例如:

  • 整型常量:100-200
  • 浮点型常量:3.14-0.51.23e4
  • 字符常量:'A''1''\n'
  • 字符串常量:"Hello""C Language"

2.5.2 符号常量

符号常量是用一个标识符来表示的常量,使用#define预处理命令定义。语法格式:

#define 常量名 常量值

例如:

#include <stdio.h>#define PI 3.14159
#define MAX_AGE 120

int main() {
    float radius = 5.0;
    float area = PI * radius * radius;
    
    printf("圆的面积 = %.2f\n", area);
    printf("最大年龄 = %d\n", MAX_AGE);
    
    return 0;
}

运行结果:

圆的面积 = 78.54
最大年龄 = 120

2.5.3 const关键字

C语言还可以使用const关键字来定义常量,这种常量称为常变量。语法格式:

const 数据类型 常量名 = 常量值;

例如:

#include <stdio.h>

int main() {
    const float PI = 3.14159;
    const int MAX_AGE = 120;
    
    float radius = 5.0;
    float area = PI * radius * radius;
    
    printf("圆的面积 = %.2f\n", area);
    printf("最大年龄 = %d\n", MAX_AGE);
    
    return 0;
}

注意事项

  • 使用#define定义的符号常量没有数据类型,只是简单的文本替换
  • 使用const定义的常变量有数据类型,编译器会进行类型检查
  • 建议优先使用const定义常量,因为它更安全

疑难解析:#define和const的区别

主要区别如下:

  • #define是预处理命令,在编译前进行文本替换;const是C语言关键字,用于定义常变量。
  • #define没有数据类型,只是简单的文本替换;const有数据类型,编译器会进行类型检查。
  • #define定义的常量没有内存地址;const定义的常变量有内存地址。
  • #define可以定义宏,带有参数;const不能定义带参数的常量。

章节练习:变量与常量

  1. 声明并初始化各种类型的变量,并输出它们的值。
  2. 使用#defineconst分别定义常量,并比较它们的使用方式。
  3. 编写程序,演示静态变量的特性。
  4. 使用sizeof运算符计算不同数据类型的大小,并使用正确的格式化字符输出结果。
  5. 编写程序,演示全局变量和局部变量的作用域和生命周期。

章节练习

  1. 声明并初始化以下变量:

    • 一个整型变量num,初始值为100
    • 一个浮点型变量price,初始值为9.99
    • 一个字符型变量ch,初始值为'X'
    • 一个布尔型变量is_valid,初始值为false
  2. 编写程序,使用sizeof运算符输出各种数据类型的内存大小,并比较不同编译器下的结果。

  3. 使用#define定义一个符号常量MAX_LENGTH,值为100,然后在程序中使用它。

  4. 使用const定义一个常变量MIN_AGE,值为18,然后在程序中使用它。

  5. 编写一个程序,演示局部变量和全局变量的作用域和生命周期。

  6. 编写一个程序,计算并输出不同数据类型的取值范围(例如:char类型的取值范围是-128到127)。

疑难解析

Q: 为什么不同编译器下,int类型的大小可能不同?

A: C标准只规定了各数据类型的最小大小,而没有规定具体大小。例如,C标准规定int类型的大小至少为16位,但具体大小由编译器决定。在32位系统上,int类型通常为32位;在64位系统上,int类型也可能为32位或64位,这取决于编译器的实现。

Q: signed和unsigned关键字有什么区别?

A: signed关键字表示有符号类型,可以表示正数、负数和零;unsigned关键字表示无符号类型,只能表示非负数。例如,signed char的取值范围是-128到127,而unsigned char的取值范围是0到255。

Q: #define和const有什么区别?

A: 主要区别如下:

  • #define是预处理命令,在编译前进行文本替换;const是C语言关键字,用于定义常变量。
  • #define没有数据类型,只是简单的文本替换;const有数据类型,编译器会进行类型检查。
  • #define定义的常量没有内存地址;const定义的常变量有内存地址。
  • #define可以定义宏,带有参数;const不能定义带参数的常量。
Q: 什么是类型转换?什么时候会发生类型转换?

A: 类型转换是指将一种数据类型转换为另一种数据类型。类型转换可以分为隐式转换和显式转换。隐式转换是由编译器自动完成的,例如将int类型的值赋值给float类型的变量时,编译器会自动将int类型转换为float类型。显式转换是通过强制类型转换运算符()来实现的,例如将float类型的值转换为int类型:int a = (int)3.14;

易错部分

  • 变量未初始化:在C语言中,局部变量如果未初始化,其值是不确定的(垃圾值),使用这样的变量可能会导致程序出错。因此,使用局部变量前一定要初始化。
  • 类型不匹配:例如,将一个超过int类型取值范围的数赋值给int类型的变量,会导致溢出错误。或者将float类型的值直接赋值给int类型的变量,会导致小数部分丢失。
  • 混淆字符和字符串:字符使用单引号括起来(如'A'),而字符串使用双引号括起来(如"A")。字符变量只能存储一个字符,而字符串需要使用字符数组或指针来存储。
  • 全局变量使用不当:过多使用全局变量会导致程序的可维护性降低,因为全局变量可以被任何函数修改,容易导致意外的副作用。因此,应该尽量少使用全局变量。
  • sizeof运算符使用错误:sizeof运算符返回的是数据类型或变量占用的内存字节数,而不是元素个数。例如,对于数组int arr[10];sizeof(arr)返回的是整个数组占用的内存字节数(40字节,假设int为4字节),而不是数组的元素个数(10)。

章节趣事

C语言中的布尔类型(bool)是在C99标准中才引入的,在此之前,C语言没有专门的布尔类型,通常使用int类型来表示布尔值,0表示false,非0表示true。

在C语言中,字符类型(char)实际上是一种整数类型,它占用1字节内存,可以表示ASCII码表中的字符。例如,字符'A'的ASCII码值是65,因此char ch = 'A';等价于char ch = 65;

sizeof运算符是C语言中唯一的一个以关键字形式出现的运算符,它不是函数。因此,在计算变量的大小时,可以省略括号,例如sizeof a等价于sizeof(a)

C语言中的变量命名规则是:变量名只能由字母、数字和下划线组成,且不能以数字开头。但实际上,C语言还支持使用Unicode字符作为变量名,只要编译器支持。不过,为了代码的可移植性,通常只使用字母、数字和下划线。