变量
变量
变量表示一些不固定的数据,也就是可以改变的数据。
基本格式为 变量类型 变量名称 ;
- 任何变量在使用之前,必须先进行定义, 只有定义了变量才会分配存储空间, 才有空间存储数据。
- 一旦给变量指明了类型,那么这个变量就只能存储这种类型的数据
- 变量名属于标识符,所以必须严格遵守标识符的命名原则
一个 C 程序中可能会有多个函数和变量,为了区分这些函数和变量,就给他们起名, 这个名称就是标识符。标识符命名规则如下
- 只能由字母(
a~z
、A~Z
)、数字、下划 线组成 - 不能包含除下划线以外的其它特殊字符串
- 不能以数字开头
- 不能是 C 语言中的关键字
- 标识符严格区分大小写, test 和 Test 是两个不同的标识符
变量的第一次赋值,我们称为初始化?
// 先定义,后初始化
int value;
// 利用 = 号往变量里面存储数据, 我们称之为给变量赋值
// 这里的 = 号,并不是数学中的“相等”,而是 C 语言中的 赋值运算符
// 赋值的时 = 号的左侧必须是变量 (998=value,错误)
value = 998;
// 定义时同时初始化
int a = 4;
// 连续定义多个变量,之间用逗号(,)号隔开
int b = 4, c = 2;
// 结合使用
int d = 3, e;
e = 5;
// 多次赋值每次赋值都会覆盖原来的值
int i = 10;
i = 20; // 修改变量的值
// 可以将一个变量存储的值赋值给另一个变量
int a = 10;
int b = a; // 相当于把a中存储的10拷贝了一份给b
为了方便阅读代码, 习惯在 = 的两侧 各加上一个 空格
常量
定义单个常量有两种方式:#define 常量名 常量值
和 const 数据类型 常量名 = 常量值;
- 常量必须在定义时就初始化,不能先定义后赋值
- 常量名通常使用大写字母,遵循标识符的命名规则
- 常量在程序运行过程中,一旦定义,就不能再修改它的值。
#define 与 const 的区别
- #define: 没有类型检查。在编译预处理阶段进行简单的文本替换,不占用内存空间,不能直接一行定义多个。仅能定义整型常量。
- const: 有类型检查。在编译时进行类型检查,有明确的数据类型,占用内存空间,可以直接一行定义多个。可以定义多种数据:整数常量、浮点数常量、数组等等。
const
提供了更好的类型安全性和调试支持,是现代 C 编程的首选方式。
#define
#include <stdio.h>
// 在程序开头使用 #define 预处理器指令
#define PI 3.14159
int main(){
printf("PI = %f\n", PI);
return 0;
}
const
#include <stdio.h>
int main(){
// 在程序中使用 const 关键字
const int MAX_SIZE = 100;
// 一行定义多个 const 常量
const int MIN_SIZE = 10, DEFAULT_SIZE = 50;
// 定义多个 const 常量推荐分开写更清晰
const double AAA = 100.0,
BBB = 200.0,
CCC = 300.0;
printf("MAX_SIZE = %d\n", MAX_SIZE);
return 0;
}
enum
在实际生活中,有些变量的取值被限定在一个确定的范围内。例如:考试评分等级为A、B、C。星期的范围是1-7。
“枚举”类型的定义中列举出所有可能的取值, 被说明为该“枚举”类型的变量取值不能超过定义的范围。
- C 语言编译器会将枚举元素作为整型常量处理,称为枚举常量。
- 枚举元素的值取决于定义时各枚举元素排列的先后顺序。未定义的情况下,元素值为前一个元素加 1,如果首个元素未定义则值为 0。
- 也可以在定义枚举类型时改变枚举元素的值
- 枚举内已经用过的标识符名称不能再用。
枚举的类型的主要作用是提高代码的可读性。枚举在本质上更接近#define
定义的整型常量,不支持浮点数和数组,多用作常量集合。了解基本使用即可。
枚举类型是基本数据类型(非构造类型),表示有限集合的特殊类型。
枚举类型的定义格式:
enum 枚举名 {
枚举元素1,
枚举元素2,
……
};
枚举的定义
#include <stdio.h>
enum Demo {
Spring, // 第一个枚举元素的值未定义,所以为 0
// Spring = 100, // 已经用过的标识符不能再用,会报错。
Summer = 10, // 第二个枚举元素定义值,所以为 10
Autumn, // 第三个枚举元素未定义,所以为前一个元素加 1,即 11
Winter = 'A', // 第四个枚举元素定义值为char,所以为 'A'对应的ASCII码值 65
A, // 第五个枚举元素未定义,所以为前一个元素加 1,即 66
//B = 1.0, // ❌第六个枚举元素定义值为浮点数,会报错
};
int main(int argc, const char * argv[]) {
// 定义枚举变量 s
// 把 enum Demo 看作一个类型整体,类比 int,那这里写法和 int s; 是一样的
// 可以在初始化时赋值
// 也可以仅标识,在后续赋值 enum Demo s;
enum Demo s = Spring;
printf("s = %d\n", s); // 0
// 可以赋值为其他枚举元素
s = Summer;
printf("s = %d\n", s); // 10
s = Autumn;
printf("s = %d\n", s); // 11
s = Winter;
printf("s = %d\n", s); // 65
s = A;
printf("s = %d\n", s); // 66
// 枚举主要是通过标识符赋值让你原本的数组更便于理解,不阻止你直接赋值为数值
// 也可以赋值为枚举元素以外的值
s = 100;
printf("s = %d\n", s); // 100
return 0;
}
枚举的最佳实践
枚举的最佳实践是使用typedef关键字定义枚举类型,这样可以提高代码的可读性。
typedef enum {
STATE_A,
STATE_B
} StateType;
// 此时state的类型为StateType,就和你使用int、double、char等类型一样
StateType state = STATE_A;
下面是一些不推荐的写法,因为它们可能会导致代码的可读性变差。
enum Season {
Spring,
Summer,
Autumn,
Winter
} s;//直接在定义枚举类型时,定义枚举变量。
//等价于 enum Season s;
s = Spring; // 等价于 s = 0;
// 也可以赋值为枚举元素以外的值
s = 3; // 等价于 s = winter;
printf("%d", s);
// 枚举变量可以省略类型,直接赋值
// 没有给枚举类型命名,只能在定义时创建变量(不推荐)
enum {
A,
B,
} Num;
作用域
C 语言中所有变量都有自己的作用域,变量定义的位置不同,其作用域也不同
-
代码块:由一对花括号
{}
包围的代码区域,内部定义的变量只能在代码块内部使用。 {}
后面可加可不加分号,可不加是因为{}
作为一个整体,已经明确的代表一个复合语句或代码块 。加上可读性更强。
局部变量
- 局部变量也称为内部变量
- 局部变量是在代码块内定义的, 其作用域仅限于代码块内, 离开该代码块后无法使用
// 同一作用域范围内不能有相同名称的变量
int call(){
int i = 998;
int i = 666; // 报错, 重复定义
return 0;
}
int main(){
int a ;
{// 代码块内独立作用域,可以使用相同变量名
int a = 998;
int b = 666;
}
b = 100; // 报错, b是另一个作用域的变量,不能访问
return 0;
}
全局变量
- 全局变量也称为外部变量,它是在代码块外部定义的变量
int i = 666;
int i = 998; // 同一作用域范围内不能有相同名称的变量
int main(){
printf("i = %d\n", i); // 可以使用
return 0;
}// 作用域结束
int call(){
printf("i = %d\n", i); // 可以使用
return 0;
}
全局变量 和 局部变量 同名时,局部变量会遮蔽(shadow)全局变量。
#include <stdio.h>
int i = 666;
int main(){
int i = 998; // 不会报错
//当全局变量和局部变量同名时,局部变量会遮蔽(shadow)全局变量。
printf("i = %d\n", i); // 998
return 0;
}
putchar 和 getchar
向屏幕输出一个字符
#include <stdio.h>
int main(){
char ch = 'a';
putchar(ch); // 输出a
}
从键盘获得一个字符,
#include <stdio.h>
int main(){
char ch;
ch = getchar();// 获取一个字符
printf("ch = %c\n", ch);
}
printf
printf 函数称之为格式输出函数,方法名称的最后一个字母 f 表示 format。其功能是按照用户指定的格式,把指定的数据输出到屏幕上
printf 有两种基本用法:
- 字符串字面量输出( 非格式化输出)
当 printf 只输出字符串字面量且不包含任何格式说明符时,可以直接打印纯文本:
#include <stdio.h>
int main(){
// 纯文本输出,不包含格式说明符
printf("Hello, World!\n");
printf("AaBb\n");
printf("这是一行纯文本\n");
return 0;
}
- 格式化输出
调用格式为:printf("格式控制字符串",输出项列表);
格式控制字符串形式: %[标志][输出宽度][.精度][长度]类型
。类型是必选,其它是可选。
例如:
#include <stdio.h>
int main(){
int a = 10;
int b = 20;
// 最简单的形式,只使用类型。输出: 10 20
printf("%d %d",a, b);
// 格式控制字符串和输出项在数量和类型上必须一一对应
printf("%d %d %d",a, b); // 危险!输出项不足会导致未定义行为
return 0;
}
类型
类型字符串用以表示输出数据的类型, 其格式符和意义如下所示
类型 | 含义 | 示例 |
---|---|---|
字符类型 | ||
c | 单个字符 | %c |
s | 字符串(char 数组) | %s |
整数类型 | ||
d / i | 有符号 10 进制整数 | %d , %i |
hd / hi | short int(短整型) | %hd , %hi |
ld / li | long int(长整型) | %ld , %li |
lld / lli | long long int(长长整型) | %lld , %lli |
u | 无符号 10 进制整数 | %u |
hu | unsigned short int | %hu |
lu | unsigned long int | %lu |
llu | unsigned long long int | %llu |
其他进制整数 | ||
o | 无符号 8 进制整数 | %o |
lo | unsigned long int(8进制) | %lo |
llo | unsigned long long int(8进制) | %llo |
x / X | 无符号 16 进制整数 | %x , %X |
lx / lX | unsigned long int(16进制) | %lx , %lX |
llx / llX | unsigned long long int(16进制) | %llx , %llX |
浮点数类型 | ||
f | float/double(默认保留 6 位小数) | %f |
lf | double(推荐用法) | %lf |
Lf | long double(扩展精度浮点型) | %Lf |
e / E | 科学计数法输出浮点数 | %e , %E |
Le / LE | long double(科学计数法) | %Le , %LE |
g / G | 自动选择最短格式输出浮点数 | %g , %G |
特殊类型 | ||
p | 指针地址 | %p |
n | 不输出字符,存储已输出字符数 | %n |
% | 输出百分号字符 % | %% |
#include <stdio.h>
int main(){
// 字符和字符串
char ch = 'A';
char str[] = "Hello";
printf("字符: %c\n", ch); // A
printf("字符串: %s\n", str); // Hello
// 各种整数类型
short short_num = 12345;
int int_num = 123456;
long long_num = 1234567890L;
long long longlong_num = 1234567890123456789LL;
printf("short: %hd\n", short_num); // 12345
printf("int: %d\n", int_num); // 123456
printf("long: %ld\n", long_num); // 1234567890
printf("long long: %lld\n", longlong_num); // 1234567890123456789
// 无符号整数
unsigned int uint_num = 4294967295U;
unsigned long long ulonglong_num = 18446744073709551615ULL;
printf("unsigned int: %u\n", uint_num); // 4294967295
printf("unsigned long long: %llu\n", ulonglong_num); // 18446744073709551615
// 不同进制输出
int num = 255;
printf("十进制: %d\n", num); // 255
printf("八进制: %o\n", num); // 377
printf("十六进制: %x\n", num); // ff
printf("十六进制(大写): %X\n", num); // FF
// long long 的不同进制
long long big_num = 255LL;
printf("long long 八进制: %llo\n", big_num); // 377
printf("long long 十六进制: %llx\n", big_num); // ff
// 浮点数
float f_num = 3.14159f;
double d_num = 3.141592653589793;
printf("float: %f\n", f_num); // 3.141590
printf("double: %lf\n", d_num); // 3.141593
printf("科学计数法: %e\n", d_num); // 3.141593e+00
printf("自动格式: %g\n", d_num); // 3.14159
// 指针地址
int *ptr = &int_num;
printf("指针地址: %p\n", ptr); // 类似 0x7fff5fbff6ac
return 0;
}
宽度
格式: printf("a = %[宽度]类型", a);
用十进制整数来指定输出的宽度, 如果实际位数多于指定宽度,则按照实际位数输出, 如果实际位数少于指定宽度则以空格补位
#include <stdio.h>
int main(){
// 实际位数小于指定宽度
int a = 1;
printf("a =|%d|\n", a); // |1|
printf("a =|%5d|\n", a); // | 1|
// 实际位数大于指定宽度
int b = 1234567;
printf("b =|%d|\n", b); // |1234567|
printf("b =|%5d|\n", b); // |1234567|
}
标志
格式: printf("a = %[标志][宽度]类型", a);
标志 | 含义 |
---|---|
- | 左对齐, 默认右对齐 |
+ | 当输出值为正数时,在输出值前面加上一个+号, 默认不显示 |
0 | 右对齐时, 用 0 填充宽度.(默认用空格填充) |
空格 | 输出值为正数时,在输出值前面加上空格, 为负数时加上负号 |
# | 对 c、s、d、u 类型无影响 |
# | 对 o 类型, 在输出时加前缀 o |
# | 对 x 类型,在输出时加前缀 0x |
#include <stdio.h>
int main(){
int a = 1;
int b = -1;
// -号标志
printf("a =|%d|\n", a); // |1|
printf("a =|%5d|\n", a); // | 1|
printf("a =|%-5d|\n", a);// |1 |
// +号标志
printf("a =|%d|\n", a); // |1|
printf("a =|%+d|\n", a);// |+1|
printf("b =|%d|\n", b); // |-1|
printf("b =|%+d|\n", b);// |-1|
// 0标志
printf("a =|%5d|\n", a); // | 1|
printf("a =|%05d|\n", a); // |00001|
// 空格标志
printf("a =|% d|\n", a); // | 1|
printf("b =|% d|\n", b); // |-1|
// #号
int c = 10;
printf("c = %o\n", c); // 12
printf("c = %#o\n", c); // 012
printf("c = %x\n", c); // a
printf("c = %#x\n", c); // 0xa
}
精度
格式: printf("a = %[精度]类型", a);
精度格式符以"."开头, 后面 跟上十进制整数, 用于指定需要输出多少位小数, 如果输出位数大于指定的精度, 则删除超出的部分
#include <stdio.h>
int main(){
double a = 3.1415926;
printf("a = %.2f\n", a); // 3.14
}
- 动态指定保留小数位数
- 格式:
printf("a = %.*f", a);
- 格式:
#include <stdio.h>
int main(){
double a = 3.1415926;
printf("a = %.*f", 2, a); // 3.14
}
- 实型(浮点类型)有效位数问题
- 对于单精度数,使用%f 格式符输出时,仅前 6~7 位是有效数字
- 对于双精度数,使用%lf 格式符输出时,前 15~16 位是有效数字
- 有效位数和精度(保留多少位)不同, 有效位数是指从第一个非零数字开始,误差不超过本数位半个单位的、精确可信的数位
- 有效位数包含小数点前的非零数位
#include <stdio.h>
int main(){
// 1234.567871093750000
float a = 1234.567890123456789;
// 1234.567890123456900
double b = 1234.567890123456789;
printf("a = %.15f\n", a); // 前8位数字是准确的, 后面的都不准确
printf("b = %.15f\n", b); // 前16位数字是准确的, 后面的都不准确
}
长度
格式: printf("a = %[长度]类型", a);
长度 | 修饰类型 | 含义 |
---|---|---|
hh | d、i、o、u、x | 输出 char |
h | d、i、o、u、x | 输出 short int |
l | d、i、o、u、x | 输出 long int |
ll | d、i、o、u、x | 输出 long long int |
#include <stdio.h>
int main(){
char a = 'a';
short int b = 123;
int c = 123;
long int d = 123;
long long int e = 123;
printf("a = %hhd\n", a); // 97
printf("b = %hd\n", b); // 123
printf("c = %d\n", c); // 123
printf("d = %ld\n", d); // 123
printf("e = %lld\n", e); // 123
}
转义字符
格式: printf("%f%%", 3.1415);
%号在格式控制字符串中有特殊含义, 所以想输出%必须添加一个转义字符
#include <stdio.h>
int main(){
printf("%f%%", 3.1415); // 输出结果3.1415%
}
scanf
scanf 函数用于接收键盘输入的内容, 是一个阻塞式函数,程序会停在 scanf 函数出现的地方。直到用户输入数据,按下回车键(输入\n
)后,scanf接收到数据才会执行后面的代码。
调用格式为:scanf("格式控制字符串", 地址列表);
地址列表项中只能传入变量地址, 变量地址可以通过&符号+变量名称的形式获取
#include <stdio.h>
int main(){
int number;
scanf("%d", &number); // 接收一个整数
printf("number = %d\n", number);
}
分隔符
如果你要输入多个数据,怎么确定输入的哪个数据是给哪个变量赋值?这就是分隔符的作用。
#include <stdio.h>
int main(){
int number;
int value;
// 推荐使用空格
// 可以输入 数字 空格 数字
scanf("%d %d", &number, &value);
printf("number = %d\n", number);
printf("value = %d\n", value);
}
如果将分隔符指定为其他形式,例如逗号需要注意中英文之分,再次推荐使用空格。
#include <stdio.h>
int main(){
int number;
int value;
// 输入 数字,数字 即可
scanf("%d,%d", &number, &value);
printf("number = %d\n", number);
printf("value = %d\n", value);
}
下面的写法相当于把分割符指定为number =
#include <stdio.h>
int main(){
int number;
// 用户必须输入number = 数字 , 否则会得到一个意外的值
// 示例:如果你希望输入10,就必须输入number = 10【回车】
scanf("number = %d", &number);
printf("number = %d\n", number);
}
忽略空白字符
接收非字符和字符串类型时, 空格、Tab 和回车会被自动忽略
#include <stdio.h>
int main(){
float num;
// 例如:输入 Tab 空格 回车 回车 Tab 空格 3.14 , 得到的结果还是3.14
scanf("%f", &num);
printf("num = %f\n", num);
}
缓冲区
- 系统会将用户输入的内容先放入输入缓冲区
- scanf 方式会从输入缓冲区中逐个取出内容赋值给变量
- 如果输入缓冲区的内容不为空,scanf 会一直从缓冲区中获取,而不要求再次输入
#include <stdio.h>
int main(){
int num1;
int num2;
char ch1;
// 如果在这里一次性输入:10a20b30
// 从缓冲区中提取整数、文本、整数
scanf("%d%c%d", &num1, &ch1, &num2);
printf("num1 = %d, ch1 = %c, num2 = %d\n", num1, ch1, num2);
char ch2;
int num3;
// 继续从缓冲区中提取文本、整数(无需输入)
scanf("%c%d",&ch2, &num3);
printf("ch2 = %c, num3 = %d\n", ch2, num3);
//
}
那我万一输入多了,影响后面的变量接收怎么办?
可以利用以下方法清空输入缓冲区:
#include <stdio.h>
int main(){
int num1;
int num2;
char ch1;
scanf("%d%c%d", &num1, &ch1, &num2);
printf("num1 = %d, ch1 = %c, num2 = %d\n", num1, ch1, num2);
// 标准方法,通过getchar获取到回车符或EOF为止。
int c;
while ((c = getchar()) != '\n' && c != EOF);
// 方法二
setbuf(stdin, NULL);
char ch2;
int num3;
scanf("%c%d",&ch2, &num3);
printf("ch2 = %c, num3 = %d\n", ch2, num3);
}