2.4 常量与变量
常数的问题
数字常量
- 数字常量
- 实数的科学计数法表示方式
- 字符常量
- 字符串常量
- 转义字符
变量
- 变量的概念
- 变量的地址
- 变量的值
- 变量的名称
notes
简述
理解在程序中直接使用常量的弊端
知道常量是程序中无法被改变的量
知道变量是程序中可以被改变的量
知道多种在程序中表示常量的方式,以及这些方式之间的差异
知道 C 语言中都有哪些常量
掌握变量的定义和初始化
掌握浮点数的科学计数表示法
直接使用常数的弊端
在 C 语言中,直接使用常数可能存在以下问题:
- 可读性差:因为常数可能没有具体的含义和描述,难以理解常数在程序中的作用。
- 代码维护困难:当需要修改常数值时,需要在程序中进行全局搜索和替换,很容易出现错误和遗漏,导致代码维护困难。
- 可移植性差:可能会依赖于特定的编译器或平台,导致代码不具备可移植性。
- 错误风险高:容易出现类型错误或计算错误,导致程序运行出错或结果不正确。
小结:
- 在程序设计中,应尽量避免直接使用常数
- 可以使用 宏定义、常量变量、枚举类型 等方法来定义常数,并在程序中使用这些定义的常数,以提高代码的可读性和可维护性
#define 宏定义
在 C 语言中,宏定义是通过
#define
指令来定义的,其语法格式如下:#define 宏名 替换文本
宏名是标识符,用来表示宏定义的名称;
- 替换文本是宏定义的具体内容,可以是任意文本,包括常数、表达式、语句、函数等;
#define PI 3.14159 // 定义常数宏
#define SQUARE(x) x*x // 定义带参数宏
#define PRINT printf // 定义函数宏
- 在宏定义中使用括号可以避免一些意外的错误,特别是带参数的宏定义中,必须使用括号来保证优先级的正确性
- 在使用宏定义时,也需要注意避免一些常见的陷阱,如多次计算、副作用等问题
const 常量变量
在 C 语言中,常量变量是通过使用
const
关键字来定义的,其语法格式如下:const 数据类型 常量名称 = 常量值;
const
是关键字,用来表示该变量是常量,不可修改;数据类型
表示常量的数据类型;常量名称
是标识符,用来表示常量的名称;常量值
是常量的具体数值,可以是任意合法的表达式或常量;
const int MAX_NUM = 100; // 定义整型常量变量
const float PI = 3.14159; // 定义浮点型常量变量
const char* MESSAGE = "Hello"; // 定义字符串常量变量
- 常量变量一旦被定义后,就不能被修改了。如果试图修改常量变量的值,将会导致编译错误。
- 常量变量的作用是为了 提高程序的可读性和可维护性,避免程序中出现不必要的硬编码常数。
- 常量变量的作用域和生命周期与普通变量相同,可以在任意位置使用和访问。
- 常量变量可以用来作为函数的参数和返回值,以及数组的维度等。
为什么推荐使用常量变量来定义常数,而不推荐使用宏定义来定义常数
推荐使用常量变量来定义常数,因为它具有数据类型的限制,可以进行类型检查,便于程序调试和维护。
在 C 语言中,宏定义和常量变量都可以用来定义常数,但它们之间有一些差异:
- 定义方式不同
- 宏定义使用
#define
指令来定义 - 常量变量则需要使用
const
关键字和数据类型来定义
- 宏定义使用
- 编译时期处理不同
- 宏定义是在预处理阶段进行处理的
- 常量变量是在编译阶段进行处理的
- 存储方式不同
- 宏定义只是简单的文本替换,不会分配存储空间
- 常量变量会分配存储空间
- 数据类型不同
- 宏定义没有数据类型的限制
- 常量变量需要指定数据类型
- 取值方式不同
- 宏定义取值时是简单的文本替换,没有类型检查
- 常量变量取值时需要进行类型转换
- 作用域不同
- 宏定义没有作用域的限制
- 常量变量的作用域和生命周期是有限制的
- 调试信息不同
- 宏定义不会出现在调试信息中
- 常量变量会出现在调试信息中
输入半径,求圆的周长和面积
#include "stdio.h"
int main() {
float r, circum, area;
printf("input r:");
scanf("%f", &r);
circum = 2 * 3.1415926 * r; // 3.1415926 常数
area = 3.1415926 * r * r;
printf("circumference = %.2f\n", circum);
printf("area = %.2f\n", area);
return 0;
}
/* 运行结果:
input r:5.3
circumference = 33.30
area = 88.25
*/
#include "stdio.h"
#define PI 3.1415926
int main() {
float r, circum, area;
printf("input r:");
scanf("%f", &r);
circum = 2 * PI * r;
area = PI * r * r;
printf("circumference = %.2f\n", circum);
printf("area = %.2f\n", area);
return 0;
}
#define PI 3.1415926
注意:#define
语句结尾不要加分号
#include "stdio.h"
int main() {
const double pi = 3.1415926;
float r, circum, area;
printf("input r:");
scanf("%f", &r);
circum = 2 * pi * r;
area = pi * r * r;
printf("circumference = %.2f\n", circum);
printf("area = %.2f\n", area);
return 0;
}
#define PI 3.1415926
VS const double pi = 3.1415926;
- const 常量具有数据类型
- 某些集成化调试工具可以对 const 常量进行调试
思考:之前的课程中,导入头文件我们使用的都是
#include <stdio.h>
而本节使用的是#include "stdio.h"
这两种写法之间是等效的嘛? 答:就上述程序,我们可以认为这两种写法都是等效的。但实际上它们是存在一些细微的差异的 👉🏻 对比两种不同的引入头文件的写法之间的差异 #include “stdio.h” 和 #include
头文件
include 是一个预处理器指令,用于包含一个头文件。
- 头文件中包含了常量、函数声明和定义、宏定义等信息。
include 指令包含的头文件分为系统头文件和用户头文件。
- 系统头文件是由编译器提供的,它们包含了一些与平台相关的函数声明和定义、宏定义等信息。这些头文件通常位于系统目录中,如
、 、 等。 - 用户头文件则是由程序员自己编写的,它们包含了自定义的函数声明和定义、宏定义等信息。这些头文件通常位于程序所在的目录中,或者自定义的目录中,如 “myheader.h”。
- 系统头文件是由编译器提供的,它们包含了一些与平台相关的函数声明和定义、宏定义等信息。这些头文件通常位于系统目录中,如
- 在 #include 指令中,头文件名可以用尖括号括起来,也可以用双引号括起来。
- 使用尖括号括起来的头文件名表示系统头文件,编译器会从系统目录中查找该文件;
- 使用双引号括起来的头文件名表示用户头文件,编译器会先在程序所在目录中查找该文件,如果找不到再去系统目录中查找。
Q:#include
A:#include
对比两种不同的引入头文件的写法之间的差异 #include “stdio.h” 和 #include
在 C 语言中,**#include**
是用来包含头文件的预处理指令,用于将头文件中定义的函数和变量导入到当前文件中以供使用。
#include "filename"
与 #include <filename>
之间的差异在于 搜索头文件的路径不同:
#include "filename"
会先在当前源文件所在目录下搜索指定的头文件,如果找到了则直接使用,如果没有找到,则在编译器指定的标准库目录中查找。#include <filename>
则只在编译器指定的标准库目录中查找指定的头文件,不会在当前源文件所在目录下搜索。
最佳实践:
- 如果头文件是自己编写的,位于当前源文件所在的目录下,应该使用
#include "filename"
- 如果头文件是标准库中已经存在的,应该使用
#include <filename>
注意:头文件包含的内容可能会与编译器和操作系统有关,因此在不同的编译器和操作系统下,可能需要使用不同的头文件和头文件路径。
整型常量
#include <stdio.h>
int main() {
// 十进制整型常量
int a = 100;
printf("a = %d\n", a);
// 八进制整型常量
int b = 012;
printf("b = %d\n", b);
// 十六进制整型常量
int c = 0x12;
printf("c = %d\n", c);
// 输出二进制整型常量
printf("binary: %d\n", 0b1100);
return 0;
}
/* 运行结果:
a = 100
b = 10
c = 18
binary: 12
*/
#include <stdio.h>
#include <limits.h>
int main() {
long int li = 123456789012345; // 长整型
unsigned int ui = 4294967295; // 无符号整型
unsigned long int uli = 12345678901234567890UL; // 无符号长整型
printf("long int: %ld, %ld to %ld\n", li, LONG_MIN, LONG_MAX);
printf("unsigned int: %u, 0 to %u\n", ui, UINT_MAX);
printf("unsigned long int: %lu, 0 to %lu\n", uli, ULONG_MAX);
return 0;
}
/* 运行结果:
long int: 123456789012345, -9223372036854775808 to 9223372036854775807
unsigned int: 4294967295, 0 to 4294967295
unsigned long int: 12345678901234567890, 0 to 18446744073709551615
*/
二进制、八进制、十进制、十六进制、位、字节
进制是一种数值表示方式,是指在数的表示中采用的基数,也叫基数或底数。目前广泛使用的进制有四种,分别是二进制、八进制、十进制、十六进制。
二进制(Binary)
- 二进制也叫二进制数,是计算机中使用的基本进位制
- 其数字只有 0 和 1 两种状态,表示方式以 2 为基数
- 在计算机中,二进制常用于表示机器指令、内存地址等信息
八进制(Octal)
- 八进制也叫八进制数,是一种基数为 8 的进位制数
- 其数字由 0、1、2、3、4、5、6、7 这 8 个数码组成
- 在计算机中,八进制常用于表示文件权限等信息
十进制(Decimal)
- 十进制也叫十进制数,以 10 为基数
- 其数字由 0、1、2、3、4、5、6、7、8、9 这 10 个数码组成
- 是人们生活中最常使用的数字系统
十六进制(Hexadecimal)
- 十六进制也叫十六进制数,以 16 为基数
- 其数字由 0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F 这 16 个数码组成
- 在计算机中,十六进制常用于表示颜色、内存地址等信息
位(bit)
- 是计算机信息处理的最小单位
- 用二进制的 0 和 1 表示
字节(byte)
- 是计算机存储数据的基本单位,一个字节等于八个二进制位(即 8 个 bit)
- 在计算机中,一个英文字母通常占用一个字节的空间,一个汉字通常占用两个字节的空间
<limits.h>
是一个 C 语言标准库头文件,用于定义一些常量和类型特性,包括整数类型的最大值和最小值,字符类型的最大值和最小值等。
下面是一些常见的宏定义:
- CHAR_BIT:定义每个字符的位数
- SCHAR_MIN:有符号字符型的最小值
- SCHAR_MAX:有符号字符型的最大值
- UCHAR_MAX:无符号字符型的最大值
- SHRT_MIN:有符号短整型的最小值
- SHRT_MAX:有符号短整型的最大值
- USHRT_MAX:无符号短整型的最大值
- INT_MIN:有符号整型的最小值
- INT_MAX:有符号整型的最大值
- UINT_MAX:无符号整型的最大值
- LONG_MIN:有符号长整型的最小值
- LONG_MAX:有符号长整型的最大值
- ULONG_MAX:无符号长整型的最大值
#include <stdio.h>
#include <limits.h>
int main() {
printf("char size: %d bits\n", CHAR_BIT);
printf("signed char min: %d\n", SCHAR_MIN);
printf("signed char max: %d\n", SCHAR_MAX);
printf("unsigned char max: %d\n", UCHAR_MAX);
printf("signed short min: %d\n", SHRT_MIN);
printf("signed short max: %d\n", SHRT_MAX);
printf("unsigned short max: %d\n", USHRT_MAX);
printf("signed int min: %d\n", INT_MIN);
printf("signed int max: %d\n", INT_MAX);
printf("unsigned int max: %u\n", UINT_MAX);
printf("signed long min: %ld\n", LONG_MIN);
printf("signed long max: %ld\n", LONG_MAX);
printf("unsigned long max: %lu\n", ULONG_MAX);
return 0;
}
/* 运行结果:
char size: 8 bits
signed char min: -128
signed char max: 127
unsigned char max: 255
signed short min: -32768
signed short max: 32767
unsigned short max: 65535
signed int min: -2147483648
signed int max: 2147483647
unsigned int max: 4294967295
signed long min: -9223372036854775808
signed long max: 9223372036854775807
unsigned long max: 18446744073709551615
*/
浮点数的科学计数法表示
浮点数的科学计数法表示形式是指:将浮点数表示为一个系数和一个指数的乘积的形式。
这种表示方法主要用于较大或较小的数,能够方便地表示非常大或非常小的浮点数,例如物理学中的粒子质量、天文学中的星球质量等。
浮点数的科学计数法表示通常用如下形式表示:a × 10^b
a
表示浮点数的尾数(也称为系数或有效数字),必须在 1 到 10 之间b
表示浮点数的指数,可以是任意整数
例:
- 浮点数
12345678.9
可以表示为1.23456789 × 10^7
- 浮点数
0.00012345
可以表示为1.2345 × 10^-4
在 C 语言中,浮点数也可以使用科学计数法表示,例如:
float f1 = 1.23456789e7; // 科学计数法表示 12345678.9
float f2 = 1.2345e-4; // 科学计数法表示 0.00012345
注意:
- 在使用浮点数时,应该注意数值的精度问题,避免出现舍入误差等问题。
- 注意指数的符号和位数,避免出现数值超出浮点数的范围而导致的溢出或下溢问题。
字符常量
- 在 C 语言中,字符常量是一种特殊的常量,用来表示单个字符或转义字符。
- 字符常量必须用单引号
'
括起来 - 字符的值必须在字符集中有对应的编码
- 字符常量和字符型变量在内存中都是以 ASCII 码形式存储的,在进行字符常量的赋值和比较时,可以使用单引号括起来的字符,也可以使用对应的 ASCII 码值,例如 ‘A’ 和 65 在赋值和比较上是等价的
常见的字符常量:
- 单个字符常量
- 例如:
'A'
、'b'
、'5'
…… - 注意:字符常量必须用 单引号 括起来,而不是双引号,双引号表示字符串常量。
- 例如:
- 转义字符常量
- 例如:
'\n'
(换行符)'\t'
(制表符)'\r'
(回车符)'\b'
(退格符)'\0'
(字符串结束符)- ……
- 转义字符常量用于表示不能直接输入的控制字符,需要使用反斜杠
\
来表示
- 例如:
例如:
char c1 = 'A'; // 单个字符常量
char c2 = '\n'; // 换行符
char c3 = '\t'; // 制表符
char c4 = '\0'; // 字符串结束符
转义字符
- 在 C 语言中,转义字符是一些特殊的字符,它们 以反斜杠
**\**
开头,用于表示一些不能直接输入的控制字符。 - 转义字符只能用于字符和字符串中,不能用于数字和其他数据类型
- 在使用时,需要注意字符集的编码方式,因为不同的字符集可能有不同的转义字符
常见的转义字符包括:
\'
:单引号\"
:双引号\\
:反斜杠\n
:换行符\t
:制表符\r
:回车符\b
:退格符\f
:换页符\v
:垂直制表符\0
:空字符,也叫字符串结束符
如果需要在字符串中输入反斜杠字符、单引号或双引号等字符,也需要使用转义字符进行表示:
char c1 = '\\'; // 反斜杠
char c2 = '\''; // 单引号
char c3 = '\"'; // 双引号
转义字符还可以用于表示一些不能直接输入的控制字符,例如制表符和换行符:
printf("Name\tAge\tGender\n"); // 使用制表符对齐输出
printf("Tom\t18\tMale\n");
printf("Lucy\t20\tFemale\n");
/* output:
Name Age Gender
Tom 18 Male
Lucy 20 Female
*/
字符串常量
- 在 C 语言中,字符串常量是由一系列字符组成的常量序列,以空字符
**'\0'**
结尾。 字符串常量必须用双引号
"
括起来:char str1[] = "Hello, world!"; // 字符串常量
在内存中,字符串常量会被存储为字符数组,其中每个字符占用一个字节,字符串的最后一个字符为空字符。
例如,上述字符串常量
"Hello, world!"
的内存表示如下:+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| H | e | l | l | o | , | | w | o | r | l | d | ! | 0 |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13|
字符串常量是不可修改的。
在 C 语言中,如果需要修改字符串中的某个字符,必须将字符串常量复制到字符数组中,并修改字符数组中的元素,例如:
char str2[] = "Hello, world!"; // 字符数组
str2[7] = '-'; // 修改字符数组中的元素
printf("%s\n", str2); // 输出修改后的字符数组 => Hello, -orld!
在定义字符数组时,如果没有指定数组大小,则编译器会根据字符串常量的长度自动计算数组大小。
- 例如,上述字符数组的大小为 13,即字符串中的字符数(不包括空字符)
符号常量
符号常量(Symbolic Constants)就是用宏定义定义的常量,也称为宏常量(Macro Constants)。
#include <stdio.h>
#define PI 3.1415926 // 定义符合常量 PI
int main() {
int r = 5;
double area = PI * r * r; // 使用符合常量 PI
printf("半径为 5 的圆面积:%.2f\n", area); // => 半径为 5 的圆面积:78.54
return 0;
}
变量
- 变量是一种用于 存储数据的占位符
- 变量是在程序执行过程中,其值可以改变的量
- 变量是程序中最基本的存储单元之一,用于在程序执行期间存储和操作各种类型的数据
- 变量可以存储不同类型的数据,如整型、字符型、浮点型等,这些数据可以是用户输入的数据、程序计算的结果或者是程序中的常量
- 使用变量可以提高程序的灵活性和可维护性,使程序能够更好地适应不同的需求
- 变量必须先定义后使用。即:先告诉编译器这个变量的类型和名称,然后才能在程序中使用这个变量
- 定义变量时,需要明确变量的数据类型和变量的名称
定义变量
变量定义的语法如下:
type variable_name;
type
表示变量的类型,可以是整型、字符型、浮点型、指针类型等variable_name
表示变量的名称,可以是由字母、数字、下划线组成的任意字符串,但是不能以数字开头
int num; // 定义了一个整型变量 num
char ch; // 定义了一个字符型变量 ch
变量定义后,编译器就对为其分配相应的存储空间,用于存储变量的值。
变量的初始化
变量的初始化,也叫给变量赋值。
可以使用赋值语句为变量赋初值,赋值语句的语法如下:
variable_name = value;
variable_name
表示要赋值的变量名称value
表示要赋给变量的值
int num; // 定义了一个整型变量 num
num = 10; // 将其赋值为 10
在程序中,可以多次给变量赋值,每次赋值可以改变变量的值。
int num;
num = 10; // 第一次赋值为 10
num = 20; // 第二次赋值为 20,改变了变量的值
注意:在使用变量之前,一定要先为其赋初值。
如果在程序中使用一个未初始化的变量,其值是不确定的,可能是一个随机的值,也可能是一个未定义的值。
变量定义之后,在读变量的值之前,一定要进行初始化
使用变量的步骤:
- 定义:变量从无到有
- 初始化:变量从没有值到有值
- 使用:读写变量的值
使用(读)未初始化的变量存在的问题:
- 如果在程序中使用了未初始化的变量,可能会出现奇怪的错误,导致程序无法正常运行
- 使用未初始化的变量会导致程序的行为不可预测,因为未初始化的变量的值是不确定的,可能是随机的垃圾值,也可能是某些编译器默认的值
- 未初始化的变量也可能会引发安全漏洞。例如,如果一个未初始化的指针变量被错误地用作内存地址,可能会导致程序崩溃或者执行恶意代码。
小结:在编写程序时应该尽可能地避免使用未初始化的变量,可以使用赋初值的方式或者在定义变量时进行初始化。
变量地址
- 含义:变量地址是指变量在计算机内存中存储的位置。
- 唯一性:每个变量在内存中都有一个 唯一 的地址,用于标识变量在内存中的位置。
- 获取变量地址:可以使用取地址符
&
来获取变量的地址
变量地址的应用
#include "stdio.h"
int main() {
int num = 10;
int* p = # // 获取变量 num 的地址,并将其赋给指针变量 p
printf("%d", *p); // 输出变量 num 的值,结果为 10
return 0;
}
int *p = #
变量 num 的地址被赋值给了指针变量 p,p 就可以用来访问变量 num 在内存中的值printf("%d", *p);
通过指针 p 来访问变量 num 的值,需要使用解引用符 * 来获取指针所指向的变量的值。
#include "stdio.h"
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
printf("Before swap: x = %d, y = %d\n", x, y);
swap(&x, &y); // 传递变量的地址
printf("After swap: x = %d, y = %d\n", x, y);
return 0;
}
/* 运行结果:
Before swap: x = 10, y = 20
After swap: x = 20, y = 10
*/
在上面的代码中,函数 swap 通过传递指针来交换变量 x 和 y 的值。
变量地址还可以用于在函数之间传递参数。在 C 语言中,函数的参数传递是通过值传递的,也就是说,函数的参数实际上是参数值的副本,而不是原始变量本身。但是,可以通过传递指针来实现对原始变量的修改。
补充:
- 变量地址的概念在指针的使用中尤为重要。
- 指针是一个变量,它存储的是另一个变量的地址,可以通过指针来访问变量的值。
- 有关指针的更多内容,会在
5. 指针
中详细介绍,上述程序若无法理解,暂不要紧。