- auto(自动变量)
- register(寄存器)
- extern(声明)
- static
- c语言基本数据类型
- 变量名的命名规则
- sizeof
- signed、unsigned与整形在内存的存储
- if else
注:局部变量,自动变量,临时变量,都是一样的,统称局部变量。
c语言中auto关键字是用来修饰自动变量的,是C语言中应用最广泛的一种类型。
在程序内定义的局部变量,如果没有被声明为其他类型的局部变量都是自动变量。
可以得出结论:没有被auto修饰的局部变量都是自动变量。
//举例 #includeauto int a = 0; //报错!全局变量不能被auto修饰 int main() { for (int i = 0; i < 2; i++){ printf("i=%dn", i); if(1) { auto int j = 0; //自动变量 printf("before: j=%dn", j); j += 1; printf("after : j=%dn", j); } return 0; }
从运行结果可以看出:
(1)不管有没有被auto修饰的局部变量,都是自动变量!
(2)也就是说,如果i用auto修饰并且去掉j的auto都是没问题的。
结论:
(1)一般在代码块定义的变量,即局部变量,默认都是auto修饰的,一般都是省略的
(2)该关键字已经很老,基本永不使用
(3)全局变量不是自动变量,也不能用auto修饰
register(寄存器)
在电脑中CPU主要是负责进行计算的硬件单元,但是为了方便运算,一般第一步需要先把数据从内存读取到CPU内,
那么也就需要CPU具有一定的数据临时存储能力。注意:CPU并不是当前要计算了,才把特定数据读到CPU里面,那样太慢了。
所以现代CPU内,都集成了一组叫做寄存器的硬件,用来做临时数据的保存
距离CPU越近的存储硬件,速度越快
寄存器的作用
本质上可以提高计算机计算的效率,因为省略了去内存读取数据这一步骤!
register 修饰变量
建议:
- 局部变量可以存储到寄存器中(全局会导致CPU寄存器被长时间占用)
- 不会被写入内存的变量(写入就需要写回内存)
- 高频被读取的(提高效率)
- 现在的编译器,已经很智能了,能够进行比人更好的代码优化,不像早期编译器需要人为指定register,来进行手动优化。
#includeint main() { register int a = 0; //a变量被存储到寄存器中 int* p = &a; //报错 return 0; }
报错的原因是:a已经被存储到寄存器中,不能被写入内存,因为地址是内存相关的概念!
extern(声明)
多文件
extern 声明变量
extern 声明函数
.c我们称之为源文件,一般包含函数实现,变量定义等
.h我们称之为头文件,一般包含函数声明,变量声明,宏定义,头文件等内容
//test.h #pragma once //防止头文件被包含 #include//c语言里面的头文件 extern int a; //声明变量,必须带上extern extern void show(int n); //声明函数,建议带上extern #define N 10 //宏定义
//main.c
#include "test.h" //""包含头文件,意思是包含自定义头文件
int main()
{
show(200); //调用函数
peintf("%dn",a); //输出变量
return 0;
}
//test.c
#include "test.h" //意思跟上面一样
int a = 100; //对变量初始化
void show(int n) //函数的实现
{
printf("hello %d worldn",n);
}
为什么要声明头文件(.h)
结论:因为组织项目结构的时候,减少大型项目维护的成本!
为什么头文件里要带上#pragma once?
结论:防止头文件被重复包含,导致程序运行效率变慢。
为什么变量声明必须带上extern?
结论:因为变量未初始化的时候,编译器会默认初始化为随机值或0。
注:声明变量是不需要开辟空间的,而定义变量并赋值是需要开辟空间的。
结论:
1.全局变量,是可以跨文件,被访问的。
2.全局函数,是可以跨文件,被访问的。
3. 所有变量声明必须带上extern
3.所有声明变量都不能初始化。
4.函数传参跨文件使用,建议在头文件声明函数和实现函数里写上参数。
注:在主函数外面定义或声明的都是全局变量。
static
想要了解static关键字的用法,请先移步到作用域和生命周期的文章c语言初识
static的作用
1.static修饰局部变量
局部变量生命周期变成全局变量生命周期,作用域不变。
2.static修饰全局变量
全局变量只能在本文件内使用。
3.static修饰函数
函数只能在本文件内使用,不能跨文件被访问(外部链接失效)。
注:static修饰全局变量影响的是作用域的概念,函数类似,生命周期不变。
static修饰局部变量
#includevoid test1() { int a = 0; //局部变量 a++; printf("No static = %d ",a); } void test2() { static a = 0; //static修饰的局部变量 a++; printf("Yes static = %d ",a); } int main() { int i = 0; for(i = 0; i < 10; i++) { test1(); putchar('n'); test2(); } return 0 }
static修饰局部变量结论:
- 在函数体内定义局部变量具有临时性,函数结束,局部变量自动被释放。
- 被static修饰后的局部变量,被存储到静态区。
- 被static修饰的局部变量,具有全局生命周期性,出了代码块不会被释放,作用域不变,只能在代码块里定义的区域使用。
static修饰全局变量
//test.h #includeextern int a; void fun(int num); fun1();
//main.c
#include "test.h"
int main()
{
printf("%dn",a); //报错,不能被外部文件直接访问
fun(100); //报错,不能被外部文件直接访问
fun1(); //通过间接访问的方式访问文件
return 0;
}
//test.c
#include "test.h"
static int a = 200; //static修饰全局变量
static void fun(int num) //static修饰函数
{
printf("%dn",num);
}
void fun1()
{
fun(50);
}
全局变量可以跨文件使用吗?
可以!
函数可以跨文件使用吗?
可以!
结论:
- static修饰全局变量,该变量只能在本文件内使用,不能被外部其他文件直接访问。
- static修饰函数,该函数只能在本文件内使用,不能被外部其他文件直接访问。
- 可以通过间接访问的方式访问文件内部的变量和函数。
c语言基本数据类型
c语言内置类型如图所示:
c语言内置类型的本质是什么?
本质:在内存中开辟空间,用来存放数据。
但是定义一个变量,是需要类型的。那么,类型决定了:变量开辟空间的大小。
| c语言类型 | 32位系统(字节) | 64位系统(字节) |
|---|---|---|
| char | 1 | 1 |
| short | 2 | 2 |
| int | 4 | 4 |
| long int | 4 | 4 |
| long long int | 8 | 8 |
| float | 4 | 4 |
| double | 8 | 8 |
| 指针 | 4 | 8 |
为什么要根据类型开辟空间?
- 因为开辟空间没有类型的话,就会造成不必要的浪费
- 一个工程里,都不是一个程序在运行,还有很多其他程序也在运行。如果我把空间一次性开辟完,其他程序就会没有空间存放数据了。
- 满足不同的计算场景。
最后如果有人还没没听懂的话,举个例子:
比如你跟朋友一起吃蛋糕,不是你自己一个人吃全部蛋糕,还有其他人在呢!另外,你自己吃整个蛋糕,一定能吃完吗?吃不完,就会造成浪费!所以说,要合理的分配蛋糕,不要贪吃。
注:蛋糕就是内存
变量名的命名规则
- 命名应当直观且可拼读,便于记忆与阅读。标识符采用英文单词或其组合,不允许使用拼音。
- 命名的长度合应符合:
“minlength&&max+information”
(最短长度,最大信息) - 避免名字中出现数字编号,如a1,a2等。除非是逻辑上或驱动开发时需要。
- 程序中不得出现仅靠大小写区分的相似标识符。 例如:(int x and int X)
- 要特别注意1和l这一对的区别,还有0和o的区别,很多人容易搞混。
- 所有宏定义,枚举常量,标识符全都用大写英文命名,用下划线分割。
- 命名规范:英文字母(a-z,A-Z),数字(0-9),中间连接可用下划线(_)。
sizeof
有什么用?
sizeof是计算变量在内存中开辟空间的大小(单位:字节(byte))。
是函数吗?
sizeof是关键字不是函数。
//举例 #includeint main() { int a; printf("%dn"sizeof(int)); //4 printf("%dn"sizeof(a)); //4 printf("%dn"sizeof int); //报错 printf("%dn"sizeof a); //4 //除了一个报错其他可以运行 return 0; }
结论:
- sizeof如果是函数的话sizeof a 就会报错,函数调用必须带括号。
- sizeof int报错原因是:不能同时存在二个关键字。
如何使用?
- sizeof在计算时,可是省略括号,但是括号内是数据类型就必须带上.
- sizeof在计算时,括号里面的变量最好不要进行运算,因为结果都是原来的值.
//举例 #includeint main() { int a = 0; printf("%dn", sizeof(a++)); //结果还是四个字节 return 0; }
- sizeof是在编译时求值,括号内进行运算是无效的.
signed、unsigned与整形在内存的存储
有符号类型 vs 无符号类型
| 有符号类型 | 取值范围(32位平台) | 无符号类型 | 取值范围(32位平台) |
|---|---|---|---|
| signed char | [-128,127] | unsigned char | [0,255] |
| signed short | [-32768,32767] | unsigned short | [0,65535] |
| signed int | [-2147483648,2147483647] | unsigned int | [0,4294967295] |
| signed long | [-2147483648,2147483647] | unsigned long | [0,4294967295] |
通用有符号类型取值范围公式:[-2n-1,2n-1-1]
通用无符号类型取值范围公式:[0,2n-1]
注:n为比特位(bit)
=>>1字节(byte)== 8比特位(bit)
原反补码
计算机中的有符号数有三种表示方法,即原码、反码和补码。
原码:将十进制按照正负数的形式翻译成二进制。
反码:原码的基础上,符号位(第一位)不变,其余依次按位取反。
补码:反码的基础上+1。
如果一个数据是正数,那么它的原反补都相同!
//举例 #includeint main() { int a = 10; //原反补码:0000 0000 0000 0000 0000 0000 0000 1010 =>10的二进制 printf("a=%dn", a); //10 unsigned int b = 10; //原反补码:0000 0000 0000 0000 0000 0000 0000 1010 =>10的二进制 printf("b=%un", b); //无符号类型输出 ==>10 int c = -1; //原码:1000 0000 0000 0000 0000 0000 0000 0001 //反码:1111 1111 1111 1111 1111 1111 1111 1110 =>符号位不变,其余取反 //补码:1111 1111 1111 1111 1111 1111 1111 1111 =>反码+1 printf("c=%dn", c); //-1 unsigned int d = -10; //1000 0000 0000 0000 0000 0000 0000 1010 //1111 1111 1111 1111 1111 1111 1111 0101 //1111 1111 1111 1111 1111 1111 1111 0110 printf("d(u)=%un", d); //4294967286 printf("d(d)=%dn", d); //-10 return 0; }
为什么无符号输出unsigned int = -10的值等于429496…呢?
- 内存中开辟的空间里面存储的是-1的补码
- 无符号输出原反补码相等,不用转换为原码打印
为什么无符号类型可以存储负数?
- 内存中开辟的空间是用来存储数据的不管是正负数都可以存储,不要被类型给迷惑了
- 但是输出看类型!
注:
- 原码是十进制转二进制的概念
- 二进制第一位是符号位==>0表示“正”,1表示“负”
- 无符号数:不需要转化,也不需要符号位,原反补相同
对于整形来说:数据存放内存中其实存放的是补码
补码转原码的方法:
- 补码-1,符号位不变,其余按位取反
- 原码到补码过程再实现一遍
十进制二进制快速转化
口诀:1后面跟n个0,就是2的n次方
例如:int a = 100;
二进制:26+25+22
0000 0000 0000 0000 0000 0000 0110 0100 ==>100
二进制快速转十进制也可以反过来用。
你学会了吗?
大小端
大端:高字节序存储到高权值位,低字节序存储到低权值位
小端:低字节序存储到高权值位,高字节序存储到低权值位
深入理解变量内容的存入和取出
//举例 #includeint main() { signed int a = -1; unsigned int b = -1; //会报错吗? //运行没有报错 return 0; }
结论
- 存:字面数据必须先转成补码,在放入空间当中。所以,所谓符号位,完全看数据本身是否携带±号。和变量是否有符号无关!
- 取:取数据一定要先看变量本身类型,然后才决定要不要看最高符号位。如果不需要,直接二进制转成十进制。如果需要,则需要转成原码,然后才能识别。(当然,最高符号位在哪里,又要明确大小端)
如何理解signed char = -128?
signed char 在内存中的分布
-128?
从补码的意义上去理解
256-128=128
所以,256+(-128)的补码=128
所以,(-128)的补码=256-128 =128
数学上,128 = 1000 0000
故而规定:-128的补码为 1000 0000
if else



