目录
补充内容
那么启动程序的本质是什么呢?将程序数据,加载到内存中,让计算机运行!程序在未运行时,在硬盘中,加载到内存中,速度较快
什么是变量(是什么)——在内存中开辟特定大小的空间,用来保存数据
变量定义的本质——我们现在已知: 1. 程序运行,需要加载到内存中 2. 程序计算,需要使用变量 那么,定义变量的本质:在内存中开辟一块空间,用来保存数据。(为何一定是内存:因为定义变量,也是程序逻辑的一部 分,程序已经被加载到内存。
变量的分类——局部变量:包含在代码块中的变量叫做局部变量。局部变量具有临时性。进入代码块,自动形成局部变量,退出代码块自动 释放。[网上很多说函数中的变量是局部变量,不能说错,但说法是不准确的] 全局变量:在所有函数外定义的变量,叫做全局变量。全局变量具有全局性。
作用域概念:指的是该变量的可以被正常访问的代码区域
局部变量:只在本代码块内有效。全局变量:整个程序运行期间,都有效。
生命周期概念:指的是该变量从定义到被释放的时间范围,所谓的释放,指的是曾经开辟的空间”被释放“
局部变量: 进入代码块,形成局部变量[开辟空间],退出代码块,"释放"局部变量
全局变量: 定义完成之后,程序运行的整个生命周期内,该变量一直都有效
1.register关键字
尽量将所修饰变量,放入CPU寄存区中,从而达到提高效率的目的
register修饰的变量,不能取地址(因为已经放在寄存区中了嘛,地址是内存相关的概念)
那么什么样的变量,可以采用register呢?
1. 局部的(全局会导致CPU寄存器被长时间占用) 比特就业课 2. 不会被写入的(写入就需要写回内存,后续还要读取检测的话,register的意义在哪呢?) 3. 高频被读取的(提高效率所在) 4. 如果要使用,请不要大量使用,因为寄存器数量有限
意见:该关键字,不用管,因为现在的编译器,已经很智能了,能够进行比人更好的代码优化。 早期编译器需要人为指定register,来进行手动优化,现在不需要了。
2.最名不符实的关键字 - static
1. 修饰全局变量,该全局变量只能在本文件内被使用,不能被其它外部文件直接访问。
2.static修饰局部变量,变量的生命周期变成全局周期。(作用域不变)
static修饰变量,它们都在内存的静态区。
3.static可修饰函数,该函数只能在本文件内被使用,不能被其它外部文件直接访问。
3.基本数据类型
4.bool
C语言有没有bool类型? c99之前,主要是c90是没有的,目前大部分书,都是认为没有的。因为书,一般都要落后于行业。 但是c99引入了_Bool类型(你没有看错,_Bool就是一个类型,不过在新增头文件stdbool.h中,被重新用宏写成了 bool,为了保证C/C++兼容性)。
//测试代码1
#include <stdio.h>
#include <stdbool.h> //没有这个头文件会报错,使用新特性一定要加上
#include <windows.h>
int main()
{
bool ret = false;
ret = true;
printf("%d\n", sizeof(ret)); //vs2013 和 Linux中都是1
system("pause");
return 0;
}
源码:#define bool _Bool //c99中是一个关键字哦,后续可以使用bool
#define false 0 //假
#define true 1 //真
int main()
{
//在vs中,光标选中BOOL,单击右键,可以看到转到定义,就能看到BOOL是什么
BOOL ret = FALSE;
ret = TRUE;
printf("%d\n", sizeof(ret)); //输出结果是4,因为在源代码中,是这么定义的:typedef int BOOL;
system("pause");
return 0;
}
微软?强烈不推荐,因为好的习惯是:一定要保证代码的跨平台性,微软定义的专属类型,其他平台不支持。
5.float 变量与"零值"进行比较
因为精度损失问题,两个浮点数,绝对不能使用==进行相等比较.
//伪代码-简洁版 if(fabs(x-y) < 精度){ //fabs是浮点数求绝对值 //TODO }
#include //使用下面两个精度,需要包含该头文件
DBL_EPSILON //double 最小精度
FLT_EPSILON //float 最小精度
6.switch,case组合
case之后,如果没有break,则会依次执行后续有效语句,直到碰到break
default可以出现在switch内的任何部分,尽管如此,我们依旧强烈推荐default应该放在case语句的最后
7.void关键字
void是否可以定义变
int main()
{
void a;
system("pause");
return 0;
}
//在vs2013和Centos 7,gcc 4.8.5下都不能编译通过
定义变量的本质:开辟空间 而void作为空类型,理论上是不应该开辟空间的,即使开了空间,也仅仅作为一个占位符看待 所以,既然无法开辟空间,那么也就无法作为正常变量使用,既然无法使用,编译器干脆不让他定义变量。 在vs2013中,sizeof(void)=0 在Linux中,sizeof(void)=1(但编译器依旧理解成,无法定义变量)
1.修饰函数返回值和参数
/如果自定义函数,或者库函数不需要返回值,那么就可以写成void 。那么问题来了,可以不写吗?不可以,自定义函数的默认返回值是int(这个现场验证) 。所以,没有返回值,如果不写void,会让阅读你代码的人产生误解:他是忘了写,还是想默认int?
int test()
{return 1;
}
int test(void)
{return 1;
}
int main()
{
printf("%d\n", test1(10)); //依旧传入参数,编译器不会告警或者报错
printf("%d\n", test2(10)); //依旧传入参数,编译器会告警(vs)或者报错(gcc)
system("pause");
return 0;
}
如果一个函数没有参数,将参数列表设置成void,是一个不错的习惯,因为可以将错误明确提前发现
8.return关键字
调用函数,形成栈帧,函数返回,释放栈帧。临时变量为什么具有临时性,栈帧结构在函数调用完,需要被释放
9.const关键字
1.const修饰变量。 让编译器进行直接修改式检查 ,告诉其他程序员(正在改你代码或者阅读你代码的)这个变量后面不要改哦。也属于一种“自描述”含义
2.const修饰数组
3.const修饰指针(const int *p,int *const p)
4.修饰函数参数(防止使用者的一些无意的或错误的修改,任何函数参数都要形成新的地址)
5.修饰函数返回值(const 修饰符也可以修饰函数的返回值,返回值不可被改变)
在另一连接文件中引用 const 只读变量: extern const int i; //正确的声明
10.volatile关键字
volatile 用它修饰的变量表示可以被某些编译器 未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编 译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。忽略编译器的优化,保持内存可见性。
补充:——const要求你不要进行写入就可以。volatile意思是你读取的时候,每次都要从内存读。 两者并不冲突。 虽然volatile就叫做易变关键字,但这里仅仅是描述它修饰的变量可能会变化,要编译器注意,并不是它要求对应变量必须 变化!这点要特别注意。
11.void关键字
void不可以定义变量,在vs2013和Centos 7,gcc 4.8.5下都不能编译通过
解释:定义变量的本质:开辟空间 而void作为空类型,理论上是不应该开辟空间的,即使开了空间,也仅仅作为一个占位符看待 所以,既然无法开辟空间,那么也就无法作为正常变量使用,既然无法使用,编译器干脆不让他定义变量。 在vs2013中,sizeof(void)=0 在Linux中,sizeof(void)=1(但编译器依旧理解成,无法定义变量)
void修饰函数返回值和参数
//如果自定义函数,或者库函数不需要返回值,那么就可以写成void
//那么问题来了,可以不写吗?不可以,自定义函数的默认返回值是int(这个现场验证)
//所以,没有返回值,如果不写void,会让阅读你代码的人产生误解:他是忘了写,还是想默认int
//void 作为函数参数
//如果一个函数没有参数,我们可以不写, 如test1()
#include <stdio.h>
#include <windows.h>
int test1() //函数默认不需要参数
{
return 1;
}
int test2(void) //明确函数不需要参数
{
return 1;
}
int main()
{
printf("%d\n", test1(10)); //依旧传入参数,编译器不会告警或者报错
printf("%d\n", test2(10)); //依旧传入参数,编译器会告警(vs)或者报错(gcc)
system("pause");
return 0;
}
//结论:如果一个函数没有参数,将参数列表设置成void,是一个不错的习惯,因为可以将错误明确提前发现
void指针
#include <windows.h>
int main()
{
void *p = NULL; //可以
system("pause");
return 0;
}
//为什么void*可以呢?因为void*是指针,是指针,空间大小就能明确出来
void* 能够接受任意指针类型
void * 定义的指针变量可以进行运算操作吗?
//在vs2013中
#include <stdio.h>
#include <windows.h>
int main()
{
void *p = NULL;
p++; //报错
p += 1; //报错
system("pause");
return 0;
}
//在gcc4.8.5中
#include <stdio.h>
int main()
{
void *p = NULL; //NULL在数值层面,就是0
p++; //能通过
printf("%d\n", p); //输出1
p += 1; //能通过
printf("%d\n", p); //输出2
return 0;
}
//为什么在不同的平台下,编译器会表现出不同的现象呢
根本原因是因为使用的C标准扩展的问题。
12.union,enum关键字
1.
联合体内,所有成员的起始地址都是一样的
union un{
int i;
char a[4];
}*p,u;
int main()
{
p=&a;
p.a[0]=0x39;
p.a[1]=0x40;
p.a[2]=0x41;
p.q[3]=0x42;
printf("0x%x",p.i);
}
0x39404142
2.
1),#define 宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。
2),一般在编译器里,可以调试枚举常量,但是不能调试宏常量。
3),枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。
13.typedef,define关键字
类型重命名,并不是文本替换,而define为文本替换
//1. 对一般类型进行重命名
//2. 对结构体类型进行重命名
//3. 对指针进行重命名
//4. 对复杂结构进行重命名(数组类型为例,先不要搞那么复杂)
举例1:
typedef int* p=int_p;
#define ptr_p int*
int main()
{ int_p a,b; //此时两个变量的类型都为int*
ptr_p a,b,c; //a类型为int*,b,c类型为int
}
举例2:
#define IN int
typedef int IM;
int main()
{unsigned IN a;
unsigned IM b;//报错
}
5个存储类型关键字