变量
变量
变量表示一些不固定的数据,也就是可以改变的数据。
基本格式为 变量类型 变量名称 ;
- 任何变量在使用之前,必须先进行定义, 只有定义了变量才会分配存储空间, 才有空间存储数据。
- 一旦给变量指明了类型,那么这个变量就只能存储这种类型的数据
- 变量名属于标识符,所以必须严格遵守标识符的命名原则
info
一个 C 程序中可能会有多个函数和变量,为了区分这些函数和变量,就给他们起名, 这个名称就是标识符。标识符命名规则如下
- 只能由字母(
a~z
、A~Z
)、数字、下划线组成 - 不能包含除下划线以外的其它特殊字符串
- 不能以数字开头
- 不能是 C 语言中的关键字
- 标识符严格区分大小写, test 和 Test 是两个不同的标识符
变量的第一次赋值,我们称为初始化?
// 先定义,后初始化
int value;
// 利用 = 号往变量里面存储数据, 我们称之为给变量赋值
// 这里的 = 号,并不是数学中的“相等”,而是 C 语言中的 赋值运算符
// 赋值的时 = 号的左侧必须是变量 (998=value,错误)
value = 998;
// 定义时同时初始化
int b = 4;
// 连续定义多个变量,之间用逗号(,)号隔开
int b = 4, c = 2;
// 结合使用
int a = 1, b = 2, c , d;
c = 2;
d = 3;
// 多次赋值每次赋值都会覆盖原来的值
int i = 10;
i = 20; // 修改变量的值
// 可以将一个变量存储的值赋值给另一个变量
int a = 10;
int b = a; // 相当于把a中存储的10拷贝了一份给b
tip
为了方便阅读代码, 习惯在 = 的两侧 各加上一个 空格
常量
定义常量有两种方式:#define 常量名 常量值
和 const 数据类型 常量名 = 常量值;
- 常量必须在定义时就初始化,不能先定义后赋值
- 常量名通常使用大写字母,遵循标识符的命名规则
- 常量在程序运行过程中, 一旦定义,就不能再修改它的值。
info
#define 与 const 的区别
- #define: 没有类型检查。在编译预处理阶段进行简单的文本替换,不占用内存空间。
- const: 有类型检查。在编译时进行类型检查,有明确的数据类型,占用内存空间。
推荐使用 const
关键字。
#include <stdio.h>
// 在程序开头使用 #define 预处理器指令
#define PI 3.14159
int main(){
// 在程序中使用 const 关键字
const int MAX_SIZE = 100;
printf("PI = %f\n", PI);
printf("MAX_SIZE = %d\n", MAX_SIZE);
return 0;
}
作用域
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;
}
输入输出
printf 函数
-
printf 函数称之为格式输出函数,方法名称的最后一个字母 f 表示 format。其功能是按照用户指定的格式,把指定的数据输出到屏幕上
-
printf 函数的调用格式为:
printf("格式控制字符串",输出项列表 );
- 例如:
printf("a = %d, b = %d",a, b);
- 非格式字符串原样输出, 格式控制字符串会被输出项列表中的数据替换
- 注意: 格式控制字符串和输出项在数量和类型上必须一一对应
-
格式控制字符串
- 形式:
%[标志][输出宽度][.精度][长度]类型
- 形式:
-
类型
- 格式:
printf("a = %类型", a);
- 类型字符串用以表示输出数据的类型, 其格式符和意义如下所示
- 格式:
类型 | 含义 |
---|---|
d | 有符号 10 进制整型 |
i | 有符号 10 进制整型 |
u | 无符号 10 进制整型 |
o | 无符号 8 进制整型 |
x | 无符号 16 进制整型 |
X | 无符号 16 进制整型 |
f | 单、双精度浮点数(默认保留 6 位小数) |
e / E | 以指数形式输出单、双精度浮点数 |
g / G | 以最短输出宽度,输出单、双精度浮点数 |
c | 字符 |
s | 字符串 |
p | 地址 |
#include <stdio.h>
int main(){
int a = 10;
int b = -10;
float c = 6.6f;
double d = 3.1415926;
double e = 10.10;
char f = 'a';
// 有符号整数(可以输出负数)
printf("a = %d\n", a); // 10
printf("a = %i\n", a); // 10
// 无符号整数(不可以输出负数)
printf("a = %u\n", a); // 10
printf("b = %u\n", b); // 429496786
// 无符号八进制整数(不可以输出负数)
printf("a = %o\n", a); // 12
printf("b = %o\n", b); // 37777777766
// 无符号十六进制整数(不可以输出负数)
printf("a = %x\n", a); // a
printf("b = %x\n", b); // fffffff6
// 无符号十六进制整数(不可以输出负数)
printf("a = %X\n", a); // A
printf("b = %X\n", b); // FFFFFFF6
// 单、双精度浮点数(默认保留6位小数)
printf("c = %f\n", c); // 6.600000
printf("d = %lf\n", d); // 3.141593
// 以指数形式输出单、双精度浮点数
printf("e = %e\n", e); // 1.010000e+001
printf("e = %E\n", e); // 1.010000E+001
// 以最短输出宽度,输出单、双精度浮点数
printf("e = %g\n", e); // 10.1
printf("e = %G\n", e); // 10.1
// 输出字符
printf("f = %c\n", f); // a
}
- 宽度
- 格式:
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 函数出现的地方, 直到接收到数据才会执行后面的代码
- printf 函数的调用格式为:
scanf("格式控制字符串", 地址列表);
- 例如:
scanf("%d", &num);
- 基本用法
- 地址列表项中只能传入变量地址, 变量地址可以通过&符号+变量名称的形式获取
#include <stdio.h>
int main(){
int number;
scanf("%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);
}
- 非格式字符串原样输入, 格式控制字符串会赋值给地址项列表项中的变量
- 不推荐这种写法
#include <stdio.h>
int main(){
int number;
// 用户必须输入number = 数字 , 否则会得到一个 意外的值
scanf("number = %d", &number);
printf("number = %d\n", number);
}
- 接收多条数据
- 格式控制字符串和地址列表项在数量和类型上必须一一对应
- 非字符和字符串情况下如果没有指定多条数据的分隔符, 可以使用空格或者回车作为分隔符(不推荐这种写法)
- 非字符和字符串情况下建议明确指定多条数据之间分隔符
#include <stdio.h>
int main(){
int number;
scanf("%d", &number);
printf("number = %d\n", number);
int value;
scanf("%d", &value);
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);
}
#include <stdio.h>
int main(){
int number;
int value;
// 输入 数字,数字 即可
scanf("%d,%d", &number, &value);
printf("number = %d\n", number);
printf("value = %d\n", value);
}
- \n 是 scanf 函数的结束符号, 所以格式化字符串中不能出现\n
#include <stdio.h>
int main(){
int number;
// 输入完毕之后按下回车无法结束输入
scanf("%d\n", &number);
printf("number = %d\n", number);
}
scanf 运行原理
- 系统会将用户输入的内容先放入输入缓冲区
- scanf 方式会从输入缓冲区中逐个取出内容赋值给变量
- 如果输入缓冲区的内容不为空,scanf 会一直从缓冲区中获取,而不要求再次输入
#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);
char ch2;
int num3;
scanf("%c%d",&ch2, &num3);
printf("ch2 = %c, num3 = %d\n", ch2, num3);
}
- 利用 fflush 方法清空缓冲区(不是所有平台都能使用)
- 格式:
fflush(stdin);
- C 和 C++的标准里从来没有定义过 fflush(stdin)
- MSDN 文档里清除的描述着"fflush on input stream is an extension to the C standard" (fflush 是在标准上扩充的函数, 不是标准函数, 所以不是所有平台都支持)
- 格式:
- 利用 setbuf 方法清空缓冲区(所有平台有效)
- 格式:
setbuf(stdin, NULL);
- 格式:
#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);
//fflush(stdin); // 清空输入缓存区
setbuf(stdin, NULL); // 清空输入缓存区
char ch2;
int num3;
scanf("%c%d",&ch2, &num3);
printf("ch2 = %c, num3 = %d\n", ch2, num3);
}
putchar 和 getchar
- putchar: 向屏幕输出一个字符
#include <stdio.h>
int main(){
char ch = 'a';
putchar(ch); // 输出a
}
- getchar: 从键盘获得一个字符
#include <stdio.h>
int main(){
char ch;
ch = getchar();// 获取一个字符
printf("ch = %c\n", ch);
}
变量内存分析
- 内存模型
- 内存模型是线性的(有序的)
- 对于 32 机而言,最大的内存地址是 2^32 次方 bit(4294967296)(4GB)
- 对于 64 机而言,最大的内存地址是 2^64 次方 bit(18446744073709552000)(171 亿 GB)
-
CPU 读写内存
-
CPU 在运作时要明确三件事
- 存储单元的地址(地址信息)
- 器件的选择,读 or 写 (控制信息)
- 读写的数据 (数据信息)
-
-
如何明确这三件事情
- 通过地址总线找到存储单元的地址
- 通过控制总线发送内存读写指令
- 通过数据总线传输需要读写的数据
- 地址总线: 地址总线宽度决定了 CPU 可以访问的物理地址空间(寻址能力)
- 例如: 地址总线的宽度是 1 位, 那么表示可以访问 0 和 1 的内存
- 例如: 地址总线的位数是 2 位, 那么表示可以访问 00、01、10、11 的内存
- 数据总线: 数据总线的位数决定 CPU 单次通信能交换的信息数量
- 例如: 数据总线:的宽度是 1 位, 那么一次可以传输 1 位二进制数据
- 例如: 地址总线的位数是 2 位,那么一次可以传输 2 位二进制数据
- 控制总线: 用来传送各种控制信号
-
写入流程
- CPU 通过地址线将找到地址为 FFFFFFFB 的内存
- CPU 通过控制线发出内存写入命令,选中存储器芯片,并通知它,要其写入数据。
- CPU 通过数据线将数据 8 送入内存 FFFFFFFB 单元中
-
读取流程
- CPU 通过地址线将找到地址为 FFFFFFFB 的内存
- CPU 通过控制线发出内存读取命令,选中存储器芯片,并通知它,将要从中读取数据
- 存储器将 FFFFFFFB 号单元中的数据 8 通过数据线送入 CPU 寄存器中
- 变量的存储原则
-
先分配字节地址大内存,然后分配字节地址小的内存(内存寻址是由大到小)
-
变量的首地址,是变量所占存 储空间字节地址(最小的那个地址 )
-
低位保存在低地址字节上,高位保存在高地址字节上
10的二进制: 0b00000000 00000000 00000000 00001010
高字节← →低字节