目录
前言
0、基础常识
(1)进制
(2)变量与常量
(3)内存
(4)其它零零碎碎的点
(5)运算符
1、关键字
1.switch
2.关键字总览(不需要记,认识即可)
2、分支和循环
3、函数
4、数组
5、指针
6、结构体
7、数据的存储
8、字符串
9、符号
10、动态内存管理
11、文件操作
12、程序的编译(预处理)
前言
今天的笔记是1.2版本,想跟大家分享一下我的笔记,很明显,目前并没有完成,想要完整的完成,我还有很长一段路要走,其中也许会有一些值得大家借鉴的地方,具体还是因人而异吧,希望大家能够有所收获,后续我还会继续补充,这次呢,重点内容我将不作标注,因为针对每个人的的个人情况不同,所以重点内容也不尽相同,希望大家能够关注我(虽然写的不太好),里面有的是一些基础,有的不是,有的是一些易错点,有的是一些边角料,更新频率的话最少一周一次,直到彻底补充完整,如果大家觉得对自己有所帮助的话,希望大家点一波关注和小小的赞吧,谢谢大家的支持!
0、基础常识
(1)进制
1.ddd表示1~3个八进制的数字,注意 71和71表示的都是八进制的数字。
(2)变量与常量
1.局部变量是指代码块内的变量,全局变量是指代码块外定义的变量。
2.定义并且初始化变量的本质是先根据变量类型所占据的内存空间大小为依据开辟空间,然后把索要存储的数据的二进制补码形式存储在内存中,就像unsigned int 类型的变量也可以存储125一样,换句话说,无论什么类型,都可以互相存储,当然,前提是开辟的内存能够放的下,因为无论存储什么数据,存放的都是二进制补码形式,只有在输出时才会进行不同形式的转换,比如-128的补码在转换为源码形式符号位不变,其它位按位取反,(假设输出类型是unsigned int),然后加1,然后转换为十进制进行输出,而如果输出类型是signed int时,就直接把补码转换为十进制即可,至于一些运算就无关紧要了,因为都是以补码形式进行计算。
3.C语言中的常量分为以下以下几种:字面常量、const 修饰的常变量(注意const修饰的最然不可被改变,但本质上仍为变量,不能在定义数组使放入[]内,因为[]内只能是常量)、#define 定义的标识符常量、枚举常量
枚举常量的定义方式
enum Sex
{ MALE, FEMALE, SECRET };
//括号中的MALE,FEMALE,SECRET是枚举常量(按照整数打印后数值为0 1 2)
4.当局部变量和全局变量同名的时候,局部变量优先使用,但一般在定义局部变量时不要和全局变量同名。
5.局部变量是指代码块内的变量,全局变量是指代码块外定义的变量。
6.全局变量静态变量在编译期间就创建好了。
7.诸如strlen等的函数名可以作为变量名,但我们并不推荐!
8.define不是关键字,是一个预处理指令,由编译器实现,可以作为变量名,但关键字不可以。
9.变量的访问原则:
(1)不允许在同一个作用域中定义多个相同名称的变量,编译器会报错,显示变量重定义。
(2)允许在不同的作用域中定义多个不同名称的变量。
(3)不同作用域中定义的变量,在访问时采用就近原则。即局部优先原则。
(3)内存
1.int 和 long(int)一般都是八个字节,事实上对于long (int)的字节长度要求是大于或者等于int类型所占的字节数。
2.强制类型转换的格式是(类型) 变量名而不是 类型 (变量名),后者是ui变量的定义,编译器会显示对变量的重定义。
3.任何有值特性的均能用sizeof()求其所占的内存的大小,比如sizeof(10),编译器一般会把整数默认为是int 类型占据4个字节,把小数默认为是double占据8个字节。
(4)其它零零碎碎的点
1.C语言中是没有次方运算的,如果要进行次方运算需要运用pow()函数,^是异或运算符。
scanf格式输入要注意同步,scanf()运用时格式时非常严格的,代码格式和输入格式要严格对照。同时注意一点,如果定义普通变量之后未初始化是无法输出的,但如果在定义之后,用scanf()进行输入操作此时不初始化也没有问题,但我们通常并不建议这样操作,因为在编写程序时,我们赋的初值有时会具有某些含义,所以无论后续是否用scanf进行输入,最好都要初始化。
2.在定义变量时,因为编译器默认为我们输入的整数是int,输入的浮点数为double,所以在定义单精度浮点数时float a = 3.14f
3.浮点数在进行比较的时候,绝对不能用==进行直接比较,浮点数在存储的时候本身会有精度损失,进而导致结果可能会有各种细微的差别。
解决方案:#define EPSILON(精度的意思) 0.00001(自己设定的精度)
if ( (x - y) >-0.00001 && (x-y) < 0.00001) 或者 if(fabs(x-y)
实际上,系统已经给我们定义好了精度,即DBL_EPSILON 用法同上,判断两个数是否相等时是if(fabs(x-y)),如果判断某一个浮点数是否等于0时可用if(fabs(x)前面不能加=,即这个数不等于0)
4.区分0、 、NULL ‘0按照整数进行输出后的结果是48(0的ascii码值)
事实上,运用printf进行输出时,格式为%d时数据都是一样的,均为0,但它们的类型是不同的。
int a = 0; int *a = NULL(类型为void *); (能够被操作符(+-*/=等)两端连接起来的数据类型必须是一样的,如果不一样会发生报错或者警告)
int *p=NULL;
推荐if(NULL == p),而不推荐if(p==0)和if(p)和if(p==NULL)
5.按%p格式进行打印是用来打印地址的。
6.如何理解强制类型转换?
在将''123456''转换为整型时需要自己写函数或者使用相应的库函数,会改变内存中的数据。(真实的转化,并不等于强制类型转换)
强制类型转换并不改变内存中任一二进制位,只改变了我们如何去解释该二进制数字,没有改变内存中的数据。(强制类型转换的本质)
7.#define _CRT_SECURE_NO_WARNINGS要放在(头)文件的最前面或者采用#pragma warning(disable:4996) (后者可以不放在最前面)
8.使用getchar()函数时要注意输入缓冲区的存在,getchar()先检索输入缓冲区中有没有字符,如果没有,才会把输入的字符加载进去,尤其注意我们通常输入的间隔符可能就会因为我们代码的不严谨而被加载到缓冲区中。
9.正数的源反补相同,负数的源反补按照上面步骤进行转换!
10.在未声明时不能用其它.c文件里的全局变量,如果要使用,需要用extern(关键字,专门用来声明外部符号的,用法为 extern 类型 变量名,全局变量的作用域是整个工程,生命周期是在程序的运行期间,而static修饰了全局变量后,就使全局变量只能在定义的文件内使用,即使在其它文件中用extern声明后也不能使用)进行声明。
一个全局变量在整个工程的其它文件内部中可以被使用是因为全局变量具有外部链接属性,当一个全局变量被static修饰后,这个变量的外部链接属性就变为了外部链接属性就变成了内部链接属性,使得这个全局变量只能在自己的源文件内使用,其它文件不能使用,给人感觉作用域变小了,生命周期没有变化,存储位置也没有发生变化。
11.内存空间的单位是字节。
12.键盘输入的内容,或者往显示器中打印的内容,全部都是字符,从printf()的返回值即可得出,因为printf的返回值就是输出字符的数目。就像getchar()输入1234,就可以通过printf()进行输出后得到1234,事实上,1234是四个字符。
无论是scanf还是getchar,输入都是以字符形式进行的,不同的是,scanf会进行格式化存储。所以我们把显示器或者键盘叫做字符设备,因为输入的是字符,输出的还是字符。
13.任何C程序,在默认编译好之后,运行时,都会打开三种输入输出流:
stdin:标准输入 FILE* stdin 键盘
stdout:标准输出 FILE* stdout 显示器
stderr:标准错误 FILE*stderr 显示器
14.计算机中的释放空间到底是指的什么?
首先,删除数据并不是把所有的数据清0/1。因为无论是清0还是清1,都是一个写入的过程,如果是清0/1的话,写入和清空应该花费同样多的时间。
计算机中清空数据,只要设置该数据无效即可
15.了解编译和链接。
(1)什么是编译和链接?
编译是为了将函数变量等变成,o二进制的机器码格式,链接是为了将各个独立分开的二进制的函数链接起来形成一个整体的二进制可执行文件。
(2)编译和链接以什么为单位?
编译以文件为单位、链接以工程为单位。
编译器编译时会将所有源文件依次读出来,以每个文件为单位进行编译,因此编译不会考虑其他的文件,显然这样就简化了编译器的设计。
链接的时候实际上是把第一步编译生成的.o文件作为输入,然后将它们链接成一个一个可执行程序,第一步有多少.c文件,编译时就会有多少个.o文件,链接后多个.o文件就会变成一个可执行文件。
(3)三种链接属性:外链接、内链接、无链接
外链接:外链接就是需要的函数与变量可以在外部文件找到,通俗说就是可以被跨文件访问。
内链接:与外链接相反,需要的函数和变量在当前的文件的内部就可以找到,或者说具有内部链接属性的变量只能在文件内部被访问,static修饰全局变量和函数都是内链接的。
无链接:这个符号本身不参与链接,它跟链接没有关系,局部变量(auto、和被static修饰的局部变量)都是无链接的。
(4)函数和全局变量的命名冲突问题
extern修饰的全局函数和全局变量都是外链接的,这些函数和变量在整个程序的所有.c文件中都是可以被访问到的,因此对于外部链接的全局函数和全局变量来说,避免命名冲突是非常重要的,特别是在一个大型的工程项目中,不出现相同的名字是很难做到的。所以在C++中给出了解决方案,就是使用命名空间namespace的方式,通俗点就是给一个变量带上各个级别的前缀,不过C语言中并没有这种方法。但是C语言也有自己的解决方案,就是使用之前的外链接、内链接和无链接这三种属性的方法。
C语言的解决方法是这样的,我们将明显不会再其它C文件中引用的全局变量/函数,使用static修饰使其成为内链接,这样在将来链接时,即使2个.c文件有重名的全局函数/变量,只要其中一个或两个为内链接就不会冲突。当然这种解决方案在一定程度上解决了这个问题,但是并没有从根本上解决问题,因此留下了一些瑕疵,今后我们在用C语言写大型项目时要格外注意命名问题。
(5)运用上面的知识分析运用static修饰全局变量和全局函数
当我们使用static修饰全局变量和全局函数的时候,他们的作用范围就被锁在了本文件内,其它文件在链接时无法使用这些函数和全局变量,这就是由原来的外链接属性变成了内链接属性,同时有限避免了函数和全局变量的命名冲突问题。
16.定义域和生命周期的概念
作用域概念:指的是该变量的可以被正常访问的代码区域(区域性的概念)
生命周期的概念:变量的创建到变量的销毁之间的一个时间段(时间上的概念)
17. 1. 全局变量,是可以跨文件,被访问的。 2. 全局函数,是可以跨文件,被访问的。
18.源反补的转换方法
原码:直接将二进制按照正负数的形式翻译成二进制就可以。 反码:将原码的符号位不变,其他位依次按位取反就可以得到了。 补码:反码+1就得到补码。
19.在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理; 同 时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码与原码相互转换,其运算过程是相同的,不 需要额外的硬件电路。计算也是用补码进行计算的!
20.printf()和scanf()在底层上也是由getchar和putchar实现的!
getchar函数是逐个字符进行读入,逐个字符进行输出的!如果进行相应的判定,自然也是逐个字符进行判定!下面例子即可证明。
21.在 16 位环境下,short 的长度为 2 个字节,int 也为 2 个字节,long 为 4 个字节。 16 位环境多用于单片机和低级嵌入式系统,在PC和服务器上已经见不到了。 对于 32 位的 Windows、Linux 和 Mac OS,short 的长度为 2 个字节,int 为 4 个字节,long 也为 4 个字节。
无论在32位还是64位的操作系统下。int 始终占据4个字节。
22.C语言语句可分为以下五类:
(1)表达式语句(例如 y=x+3;假设变量y和x均已定义)
(2)函数调用语句(MAX(x,y);假设函数MAX()已经定义)
(3)控制语句(if 、switch等等)
(4)复合语句(把多种语句复合在一起形成的语句)
(5)空语句(分号本身就可以作为一条语句,称为空语句 )
无论上述哪一种语句,都必须以分号结束。
23.有关于getchar和putchar
getchar的作用:
从一个流里读取一个字符,或者从标准输入(键盘)里面获取一个字符。
getchar的返回类型:int(存储文件结束标志-1)
putchar的作用:
写一个字符到流里面或者到标准输出(显示器)中。
putchar的输出类型:字符型
注意:在使用getchar时,如果前面输入了空格到了缓冲区中,不要忘了用getchar吸收缓冲区中的数据!
ctrl+z就是文件结束标志,使getchar终止
24.C程序地址空间:
程序在编译之前,main()就已经先预定好了分配的空间,即先开辟了空间,在main函数中定义的变量,都是在这段空间中重新申请空间来存放的,即二次开辟。我们把给函数预先分配的这块空间叫做栈帧。
这些函数开辟的那块空间也叫栈帧。在其它局部函数中定义变量也是在相应的栈帧上开辟的。
注意:调用函数时相应的栈帧开辟,调用结束后函数返回,释放栈帧。
#include
#include
char* show()
{
char str[] = "hello bit";
return str;
}
int main()
{
char* s = show();
printf("%sn", s);
system("pause");
return 0;
}
释放栈帧之后,里面保存的数据并没有被清除掉,因为计算机意义上的删除只是使数据无效化,无效化指的是这段内存空间是可被覆盖的,换言之,栈帧释放掉后,内存中仍然存储着之前释放栈帧中相应的数据,数据并没有随着栈帧的释放而被清空,在这个函数中show()函数返回的时候,str仍然指向"hello world"这个字符串。
通过监视函数可以看到,在调用printf()函数之前,字符串"hello world"还存在于内存中,当调用完printf()函数之后,字符串"hello world"在内存中就不存在了。前者的原因是计算机并不清空数据,虽然这些数据无效了,但我们依旧可以看到(因为它们仍然存在于内存空间中),后者的原因是因为printf()也是函数,也遵循上面的规则,调用printf()形成新的栈帧,调用结束,释放新的栈帧,新的栈帧会覆盖旧的栈帧结构,进行覆盖之后,原先的hello world字符串自然就不存在了,或者说是被改变了,当然,因为前面旧的栈帧被释放了,即无效了,所以才能被覆盖。
虽然编译器在编译的时候,不会真正的调用我们定义的函数,但编译器会根据我们定义的变量(类型数量),来预估出我们需要的空间大小,进而根据空间大小来开辟相应的栈帧,这也是为什么我们能够通过sizeof()关键字来求某某一变量的大小,因为在编译器在预估空间大小的时候就用到了sizeof()关键字来预估变量的大小。,当然,在这个过程中并没有真正的开辟空间,只有在调用的时候才会在真正的开辟相应的栈帧。
递归的过程实际上就是不断开辟栈帧的过程,如果超过预估的空间大小就会形成所谓的栈溢出的现象。
为什么临时变量具有临时性?
绝大部分变量,都是在栈上开辟的,即在函数内定义的,函数调用,形成栈帧,函数调用结束,栈帧被释放,而通过栈帧结构赖以生存的临时变量自然也就无法继续存在了,也应该被释放掉,即可以被覆盖掉了。总结来说,就是栈帧结构在函数调用完毕后,需要被释放。形象来说,就是皮之不存,毛将焉附?
return 语句不可返回指向占栈内存的指针,因为该内存在函数体结束时将自动被销毁。
问题:return后面的值是如何返回的?
return 返回数据的时候,先将对应的数据放在寄存器中,然后再将寄存器中的数据放到要保存的栈帧的对应变量的空间中,即函数的返回值,通过寄存器的方式,返回给函数调用方,这个过程,大多时候是运用的eax通用寄存器。
问题:如果不接收return返回的数据们这算数据将存放在哪呢?
仍然会放在寄存器中,只不过不再进行接收操作,即将数据返回到调用方。
25.int *a,b;这样写的时候,前面的a是指针类型,后面的b是整型数据类型
当然,我们也可以这样写:int *a=NULL,b = 0;我们并不推荐上述这两种写法,推荐的是下面这种写法:
int *a = NULL;
int b = 0;
(5)运算符
1.取模运算符%只能用于整数,即两侧只能是整数。
2.运算符优先级
3.&运算符取的永远是最低的那个地址。在C语言中,任何变量&都是最低地址开始。
4.在类型相同的情况下,对指针进行解引用,表示的就是指针所指向的目标。
int a = 10;
int *p = &a;
*p = 20;/
/
/
/ i; //正确
char* s = "abcdefgh //hijklmn"; //正确
//Is it a
valid comment? //正确
int j; //报错
system("pause");
return 0;
}
注意:注释被替换是在预处理阶段实现的,注释被替换,本质是替换成空格,上述报错的那一句本质上应该是 in t i;编译器自然会报错,报错是在预处理阶段进行语法检查时出错的,出现了语法错误。
(2)
#defineIDreplacementlist
//这段代码指的就是用replacement list替换ID
intabcdefgreplacementlist
上述两段代码都能编译通过,这说明# 和 define之间可以带空格。
//是C++风格的注释,而则是C语言风格的注释,前者可以一次写多个,不过从第一个//往后就都是注释的内容,
(3)
注意:不能嵌套注释。进行匹配。例如:
*/
最终第一行和第三行中的*/进行匹配,剩下最后一个*/。
(4)
注意下面这段代码
int x = 10 ;
int y = 10;
int z = 5;
int *p = &z;
y = x/*p;
这种代码一定要注意,/*容易被编译器认为是注释,所以会报错。
解决方案有两种:
1.y = x / *p;即在/后面加一个空格,不要让/*连在一起。
2.y = x/(*p);(推荐用这一种)。
(5)条件编译
1.
#include
#include
#define MonEY 1(只有定义了前面的宏,ifdef到endif中间的这段代码才能够正常运行,未定义则跳过)
int main()
{
#ifdef MonEY
printf("for test1n"); //test1
printf("for test2n"); //test2
#endif
system("pause");
return 0;
}
2.也可以通过if(0)来进行注释,但并不推荐,严重不推荐。
10、动态内存管理
11、文件操作
12、程序的编译(预处理)
今天的笔记是1.2版本,想跟大家分享一下我的笔记,很明显,目前并没有完成,想要完整的完成,我还有很长一段路要走,其中也许会有一些值得大家借鉴的地方,具体还是因人而异吧,希望大家能够有所收获,后续我还会继续补充,这次呢,重点内容我将不作标注,因为针对每个人的的个人情况不同,所以重点内容也不尽相同,希望大家能够关注我(虽然写的不太好),里面有的是一些基础,有的不是,有的是一些易错点,有的是一些边角料,更新频率的话最少一周一次,直到彻底补充完整,如果大家觉得对自己有所帮助的话,希望大家点一波关注和小小的赞吧,谢谢大家的支持!
(1)进制
1.ddd表示1~3个八进制的数字,注意 71和71表示的都是八进制的数字。
(2)变量与常量
1.局部变量是指代码块内的变量,全局变量是指代码块外定义的变量。
2.定义并且初始化变量的本质是先根据变量类型所占据的内存空间大小为依据开辟空间,然后把索要存储的数据的二进制补码形式存储在内存中,就像unsigned int 类型的变量也可以存储125一样,换句话说,无论什么类型,都可以互相存储,当然,前提是开辟的内存能够放的下,因为无论存储什么数据,存放的都是二进制补码形式,只有在输出时才会进行不同形式的转换,比如-128的补码在转换为源码形式符号位不变,其它位按位取反,(假设输出类型是unsigned int),然后加1,然后转换为十进制进行输出,而如果输出类型是signed int时,就直接把补码转换为十进制即可,至于一些运算就无关紧要了,因为都是以补码形式进行计算。
3.C语言中的常量分为以下以下几种:字面常量、const 修饰的常变量(注意const修饰的最然不可被改变,但本质上仍为变量,不能在定义数组使放入[]内,因为[]内只能是常量)、#define 定义的标识符常量、枚举常量
枚举常量的定义方式
enum Sex
{ MALE, FEMALE, SECRET };
//括号中的MALE,FEMALE,SECRET是枚举常量(按照整数打印后数值为0 1 2)
4.当局部变量和全局变量同名的时候,局部变量优先使用,但一般在定义局部变量时不要和全局变量同名。
5.局部变量是指代码块内的变量,全局变量是指代码块外定义的变量。
6.全局变量静态变量在编译期间就创建好了。
7.诸如strlen等的函数名可以作为变量名,但我们并不推荐!
8.define不是关键字,是一个预处理指令,由编译器实现,可以作为变量名,但关键字不可以。
9.变量的访问原则:
(1)不允许在同一个作用域中定义多个相同名称的变量,编译器会报错,显示变量重定义。
(2)允许在不同的作用域中定义多个不同名称的变量。
(3)不同作用域中定义的变量,在访问时采用就近原则。即局部优先原则。
(3)内存
1.int 和 long(int)一般都是八个字节,事实上对于long (int)的字节长度要求是大于或者等于int类型所占的字节数。
2.强制类型转换的格式是(类型) 变量名而不是 类型 (变量名),后者是ui变量的定义,编译器会显示对变量的重定义。
3.任何有值特性的均能用sizeof()求其所占的内存的大小,比如sizeof(10),编译器一般会把整数默认为是int 类型占据4个字节,把小数默认为是double占据8个字节。
(4)其它零零碎碎的点
1.C语言中是没有次方运算的,如果要进行次方运算需要运用pow()函数,^是异或运算符。
scanf格式输入要注意同步,scanf()运用时格式时非常严格的,代码格式和输入格式要严格对照。同时注意一点,如果定义普通变量之后未初始化是无法输出的,但如果在定义之后,用scanf()进行输入操作此时不初始化也没有问题,但我们通常并不建议这样操作,因为在编写程序时,我们赋的初值有时会具有某些含义,所以无论后续是否用scanf进行输入,最好都要初始化。
2.在定义变量时,因为编译器默认为我们输入的整数是int,输入的浮点数为double,所以在定义单精度浮点数时float a = 3.14f
3.浮点数在进行比较的时候,绝对不能用==进行直接比较,浮点数在存储的时候本身会有精度损失,进而导致结果可能会有各种细微的差别。
解决方案:#define EPSILON(精度的意思) 0.00001(自己设定的精度)
if ( (x - y) >-0.00001 && (x-y) < 0.00001) 或者 if(fabs(x-y)
实际上,系统已经给我们定义好了精度,即DBL_EPSILON 用法同上,判断两个数是否相等时是if(fabs(x-y)),如果判断某一个浮点数是否等于0时可用if(fabs(x)前面不能加=,即这个数不等于0)
4.区分0、 、NULL ‘0按照整数进行输出后的结果是48(0的ascii码值)
事实上,运用printf进行输出时,格式为%d时数据都是一样的,均为0,但它们的类型是不同的。
int a = 0; int *a = NULL(类型为void *); (能够被操作符(+-*/=等)两端连接起来的数据类型必须是一样的,如果不一样会发生报错或者警告)
int *p=NULL;
推荐if(NULL == p),而不推荐if(p==0)和if(p)和if(p==NULL)
5.按%p格式进行打印是用来打印地址的。
6.如何理解强制类型转换?
在将''123456''转换为整型时需要自己写函数或者使用相应的库函数,会改变内存中的数据。(真实的转化,并不等于强制类型转换)
强制类型转换并不改变内存中任一二进制位,只改变了我们如何去解释该二进制数字,没有改变内存中的数据。(强制类型转换的本质)
7.#define _CRT_SECURE_NO_WARNINGS要放在(头)文件的最前面或者采用#pragma warning(disable:4996) (后者可以不放在最前面)
8.使用getchar()函数时要注意输入缓冲区的存在,getchar()先检索输入缓冲区中有没有字符,如果没有,才会把输入的字符加载进去,尤其注意我们通常输入的间隔符可能就会因为我们代码的不严谨而被加载到缓冲区中。
9.正数的源反补相同,负数的源反补按照上面步骤进行转换!
10.在未声明时不能用其它.c文件里的全局变量,如果要使用,需要用extern(关键字,专门用来声明外部符号的,用法为 extern 类型 变量名,全局变量的作用域是整个工程,生命周期是在程序的运行期间,而static修饰了全局变量后,就使全局变量只能在定义的文件内使用,即使在其它文件中用extern声明后也不能使用)进行声明。
一个全局变量在整个工程的其它文件内部中可以被使用是因为全局变量具有外部链接属性,当一个全局变量被static修饰后,这个变量的外部链接属性就变为了外部链接属性就变成了内部链接属性,使得这个全局变量只能在自己的源文件内使用,其它文件不能使用,给人感觉作用域变小了,生命周期没有变化,存储位置也没有发生变化。
11.内存空间的单位是字节。
12.键盘输入的内容,或者往显示器中打印的内容,全部都是字符,从printf()的返回值即可得出,因为printf的返回值就是输出字符的数目。就像getchar()输入1234,就可以通过printf()进行输出后得到1234,事实上,1234是四个字符。
无论是scanf还是getchar,输入都是以字符形式进行的,不同的是,scanf会进行格式化存储。所以我们把显示器或者键盘叫做字符设备,因为输入的是字符,输出的还是字符。
13.任何C程序,在默认编译好之后,运行时,都会打开三种输入输出流:
stdin:标准输入 FILE* stdin 键盘
stdout:标准输出 FILE* stdout 显示器
stderr:标准错误 FILE*stderr 显示器
14.计算机中的释放空间到底是指的什么?
首先,删除数据并不是把所有的数据清0/1。因为无论是清0还是清1,都是一个写入的过程,如果是清0/1的话,写入和清空应该花费同样多的时间。
计算机中清空数据,只要设置该数据无效即可
15.了解编译和链接。
(1)什么是编译和链接?
编译是为了将函数变量等变成,o二进制的机器码格式,链接是为了将各个独立分开的二进制的函数链接起来形成一个整体的二进制可执行文件。
(2)编译和链接以什么为单位?
编译以文件为单位、链接以工程为单位。
编译器编译时会将所有源文件依次读出来,以每个文件为单位进行编译,因此编译不会考虑其他的文件,显然这样就简化了编译器的设计。
链接的时候实际上是把第一步编译生成的.o文件作为输入,然后将它们链接成一个一个可执行程序,第一步有多少.c文件,编译时就会有多少个.o文件,链接后多个.o文件就会变成一个可执行文件。
(3)三种链接属性:外链接、内链接、无链接
外链接:外链接就是需要的函数与变量可以在外部文件找到,通俗说就是可以被跨文件访问。
内链接:与外链接相反,需要的函数和变量在当前的文件的内部就可以找到,或者说具有内部链接属性的变量只能在文件内部被访问,static修饰全局变量和函数都是内链接的。
无链接:这个符号本身不参与链接,它跟链接没有关系,局部变量(auto、和被static修饰的局部变量)都是无链接的。
(4)函数和全局变量的命名冲突问题
extern修饰的全局函数和全局变量都是外链接的,这些函数和变量在整个程序的所有.c文件中都是可以被访问到的,因此对于外部链接的全局函数和全局变量来说,避免命名冲突是非常重要的,特别是在一个大型的工程项目中,不出现相同的名字是很难做到的。所以在C++中给出了解决方案,就是使用命名空间namespace的方式,通俗点就是给一个变量带上各个级别的前缀,不过C语言中并没有这种方法。但是C语言也有自己的解决方案,就是使用之前的外链接、内链接和无链接这三种属性的方法。
C语言的解决方法是这样的,我们将明显不会再其它C文件中引用的全局变量/函数,使用static修饰使其成为内链接,这样在将来链接时,即使2个.c文件有重名的全局函数/变量,只要其中一个或两个为内链接就不会冲突。当然这种解决方案在一定程度上解决了这个问题,但是并没有从根本上解决问题,因此留下了一些瑕疵,今后我们在用C语言写大型项目时要格外注意命名问题。
(5)运用上面的知识分析运用static修饰全局变量和全局函数
当我们使用static修饰全局变量和全局函数的时候,他们的作用范围就被锁在了本文件内,其它文件在链接时无法使用这些函数和全局变量,这就是由原来的外链接属性变成了内链接属性,同时有限避免了函数和全局变量的命名冲突问题。
16.定义域和生命周期的概念
作用域概念:指的是该变量的可以被正常访问的代码区域(区域性的概念)
生命周期的概念:变量的创建到变量的销毁之间的一个时间段(时间上的概念)
17. 1. 全局变量,是可以跨文件,被访问的。 2. 全局函数,是可以跨文件,被访问的。
18.源反补的转换方法
原码:直接将二进制按照正负数的形式翻译成二进制就可以。 反码:将原码的符号位不变,其他位依次按位取反就可以得到了。 补码:反码+1就得到补码。
19.在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理; 同 时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码与原码相互转换,其运算过程是相同的,不 需要额外的硬件电路。计算也是用补码进行计算的!
20.printf()和scanf()在底层上也是由getchar和putchar实现的!
getchar函数是逐个字符进行读入,逐个字符进行输出的!如果进行相应的判定,自然也是逐个字符进行判定!下面例子即可证明。
21.在 16 位环境下,short 的长度为 2 个字节,int 也为 2 个字节,long 为 4 个字节。 16 位环境多用于单片机和低级嵌入式系统,在PC和服务器上已经见不到了。 对于 32 位的 Windows、Linux 和 Mac OS,short 的长度为 2 个字节,int 为 4 个字节,long 也为 4 个字节。
无论在32位还是64位的操作系统下。int 始终占据4个字节。
22.C语言语句可分为以下五类:
(1)表达式语句(例如 y=x+3;假设变量y和x均已定义)
(2)函数调用语句(MAX(x,y);假设函数MAX()已经定义)
(3)控制语句(if 、switch等等)
(4)复合语句(把多种语句复合在一起形成的语句)
(5)空语句(分号本身就可以作为一条语句,称为空语句 )
无论上述哪一种语句,都必须以分号结束。
23.有关于getchar和putchar
getchar的作用:
从一个流里读取一个字符,或者从标准输入(键盘)里面获取一个字符。
getchar的返回类型:int(存储文件结束标志-1)
putchar的作用:
写一个字符到流里面或者到标准输出(显示器)中。
putchar的输出类型:字符型
注意:在使用getchar时,如果前面输入了空格到了缓冲区中,不要忘了用getchar吸收缓冲区中的数据!
ctrl+z就是文件结束标志,使getchar终止
24.C程序地址空间:
程序在编译之前,main()就已经先预定好了分配的空间,即先开辟了空间,在main函数中定义的变量,都是在这段空间中重新申请空间来存放的,即二次开辟。我们把给函数预先分配的这块空间叫做栈帧。
这些函数开辟的那块空间也叫栈帧。在其它局部函数中定义变量也是在相应的栈帧上开辟的。
注意:调用函数时相应的栈帧开辟,调用结束后函数返回,释放栈帧。
#include
#include
char* show()
{
char str[] = "hello bit";
return str;
}
int main()
{
char* s = show();
printf("%sn", s);
system("pause");
return 0;
}
释放栈帧之后,里面保存的数据并没有被清除掉,因为计算机意义上的删除只是使数据无效化,无效化指的是这段内存空间是可被覆盖的,换言之,栈帧释放掉后,内存中仍然存储着之前释放栈帧中相应的数据,数据并没有随着栈帧的释放而被清空,在这个函数中show()函数返回的时候,str仍然指向"hello world"这个字符串。
通过监视函数可以看到,在调用printf()函数之前,字符串"hello world"还存在于内存中,当调用完printf()函数之后,字符串"hello world"在内存中就不存在了。前者的原因是计算机并不清空数据,虽然这些数据无效了,但我们依旧可以看到(因为它们仍然存在于内存空间中),后者的原因是因为printf()也是函数,也遵循上面的规则,调用printf()形成新的栈帧,调用结束,释放新的栈帧,新的栈帧会覆盖旧的栈帧结构,进行覆盖之后,原先的hello world字符串自然就不存在了,或者说是被改变了,当然,因为前面旧的栈帧被释放了,即无效了,所以才能被覆盖。
虽然编译器在编译的时候,不会真正的调用我们定义的函数,但编译器会根据我们定义的变量(类型数量),来预估出我们需要的空间大小,进而根据空间大小来开辟相应的栈帧,这也是为什么我们能够通过sizeof()关键字来求某某一变量的大小,因为在编译器在预估空间大小的时候就用到了sizeof()关键字来预估变量的大小。,当然,在这个过程中并没有真正的开辟空间,只有在调用的时候才会在真正的开辟相应的栈帧。
递归的过程实际上就是不断开辟栈帧的过程,如果超过预估的空间大小就会形成所谓的栈溢出的现象。
为什么临时变量具有临时性?
绝大部分变量,都是在栈上开辟的,即在函数内定义的,函数调用,形成栈帧,函数调用结束,栈帧被释放,而通过栈帧结构赖以生存的临时变量自然也就无法继续存在了,也应该被释放掉,即可以被覆盖掉了。总结来说,就是栈帧结构在函数调用完毕后,需要被释放。形象来说,就是皮之不存,毛将焉附?
return 语句不可返回指向占栈内存的指针,因为该内存在函数体结束时将自动被销毁。
问题:return后面的值是如何返回的?
return 返回数据的时候,先将对应的数据放在寄存器中,然后再将寄存器中的数据放到要保存的栈帧的对应变量的空间中,即函数的返回值,通过寄存器的方式,返回给函数调用方,这个过程,大多时候是运用的eax通用寄存器。
问题:如果不接收return返回的数据们这算数据将存放在哪呢?
仍然会放在寄存器中,只不过不再进行接收操作,即将数据返回到调用方。
25.int *a,b;这样写的时候,前面的a是指针类型,后面的b是整型数据类型
当然,我们也可以这样写:int *a=NULL,b = 0;我们并不推荐上述这两种写法,推荐的是下面这种写法:
int *a = NULL;
int b = 0;
(5)运算符
1.取模运算符%只能用于整数,即两侧只能是整数。
2.运算符优先级
3.&运算符取的永远是最低的那个地址。在C语言中,任何变量&都是最低地址开始。
4.在类型相同的情况下,对指针进行解引用,表示的就是指针所指向的目标。
int a = 10;
int *p = &a;
*p = 20;/
/
/
/ i; //正确
char* s = "abcdefgh //hijklmn"; //正确
//Is it a
valid comment? //正确
int j; //报错
system("pause");
return 0;
}
注意:注释被替换是在预处理阶段实现的,注释被替换,本质是替换成空格,上述报错的那一句本质上应该是 in t i;编译器自然会报错,报错是在预处理阶段进行语法检查时出错的,出现了语法错误。
(2)
#defineIDreplacementlist
//这段代码指的就是用replacement list替换ID
intabcdefgreplacementlist
上述两段代码都能编译通过,这说明# 和 define之间可以带空格。
//是C++风格的注释,而则是C语言风格的注释,前者可以一次写多个,不过从第一个//往后就都是注释的内容,
(3)
注意:不能嵌套注释。进行匹配。例如:
*/
最终第一行和第三行中的*/进行匹配,剩下最后一个*/。
(4)
注意下面这段代码
int x = 10 ;
int y = 10;
int z = 5;
int *p = &z;
y = x/*p;
这种代码一定要注意,/*容易被编译器认为是注释,所以会报错。
解决方案有两种:
1.y = x / *p;即在/后面加一个空格,不要让/*连在一起。
2.y = x/(*p);(推荐用这一种)。
(5)条件编译
1.
#include
#include
#define MonEY 1(只有定义了前面的宏,ifdef到endif中间的这段代码才能够正常运行,未定义则跳过)
int main()
{
#ifdef MonEY
printf("for test1n"); //test1
printf("for test2n"); //test2
#endif
system("pause");
return 0;
}
2.也可以通过if(0)来进行注释,但并不推荐,严重不推荐。
10、动态内存管理
11、文件操作
12、程序的编译(预处理)
1.ddd表示1~3个八进制的数字,注意 71和71表示的都是八进制的数字。
1.局部变量是指代码块内的变量,全局变量是指代码块外定义的变量。
2.定义并且初始化变量的本质是先根据变量类型所占据的内存空间大小为依据开辟空间,然后把索要存储的数据的二进制补码形式存储在内存中,就像unsigned int 类型的变量也可以存储125一样,换句话说,无论什么类型,都可以互相存储,当然,前提是开辟的内存能够放的下,因为无论存储什么数据,存放的都是二进制补码形式,只有在输出时才会进行不同形式的转换,比如-128的补码在转换为源码形式符号位不变,其它位按位取反,(假设输出类型是unsigned int),然后加1,然后转换为十进制进行输出,而如果输出类型是signed int时,就直接把补码转换为十进制即可,至于一些运算就无关紧要了,因为都是以补码形式进行计算。
3.C语言中的常量分为以下以下几种:字面常量、const 修饰的常变量(注意const修饰的最然不可被改变,但本质上仍为变量,不能在定义数组使放入[]内,因为[]内只能是常量)、#define 定义的标识符常量、枚举常量
枚举常量的定义方式
enum Sex
{ MALE, FEMALE, SECRET };
//括号中的MALE,FEMALE,SECRET是枚举常量(按照整数打印后数值为0 1 2)
4.当局部变量和全局变量同名的时候,局部变量优先使用,但一般在定义局部变量时不要和全局变量同名。
5.局部变量是指代码块内的变量,全局变量是指代码块外定义的变量。
6.全局变量静态变量在编译期间就创建好了。
7.诸如strlen等的函数名可以作为变量名,但我们并不推荐!
8.define不是关键字,是一个预处理指令,由编译器实现,可以作为变量名,但关键字不可以。
9.变量的访问原则:
(1)不允许在同一个作用域中定义多个相同名称的变量,编译器会报错,显示变量重定义。
(2)允许在不同的作用域中定义多个不同名称的变量。
(3)不同作用域中定义的变量,在访问时采用就近原则。即局部优先原则。
(3)内存
1.int 和 long(int)一般都是八个字节,事实上对于long (int)的字节长度要求是大于或者等于int类型所占的字节数。
2.强制类型转换的格式是(类型) 变量名而不是 类型 (变量名),后者是ui变量的定义,编译器会显示对变量的重定义。
3.任何有值特性的均能用sizeof()求其所占的内存的大小,比如sizeof(10),编译器一般会把整数默认为是int 类型占据4个字节,把小数默认为是double占据8个字节。
(4)其它零零碎碎的点
1.C语言中是没有次方运算的,如果要进行次方运算需要运用pow()函数,^是异或运算符。
scanf格式输入要注意同步,scanf()运用时格式时非常严格的,代码格式和输入格式要严格对照。同时注意一点,如果定义普通变量之后未初始化是无法输出的,但如果在定义之后,用scanf()进行输入操作此时不初始化也没有问题,但我们通常并不建议这样操作,因为在编写程序时,我们赋的初值有时会具有某些含义,所以无论后续是否用scanf进行输入,最好都要初始化。
2.在定义变量时,因为编译器默认为我们输入的整数是int,输入的浮点数为double,所以在定义单精度浮点数时float a = 3.14f
3.浮点数在进行比较的时候,绝对不能用==进行直接比较,浮点数在存储的时候本身会有精度损失,进而导致结果可能会有各种细微的差别。
解决方案:#define EPSILON(精度的意思) 0.00001(自己设定的精度)
if ( (x - y) >-0.00001 && (x-y) < 0.00001) 或者 if(fabs(x-y)
实际上,系统已经给我们定义好了精度,即DBL_EPSILON 用法同上,判断两个数是否相等时是if(fabs(x-y)),如果判断某一个浮点数是否等于0时可用if(fabs(x)前面不能加=,即这个数不等于0)
4.区分0、 、NULL ‘0按照整数进行输出后的结果是48(0的ascii码值)
事实上,运用printf进行输出时,格式为%d时数据都是一样的,均为0,但它们的类型是不同的。
int a = 0; int *a = NULL(类型为void *); (能够被操作符(+-*/=等)两端连接起来的数据类型必须是一样的,如果不一样会发生报错或者警告)
int *p=NULL;
推荐if(NULL == p),而不推荐if(p==0)和if(p)和if(p==NULL)
5.按%p格式进行打印是用来打印地址的。
6.如何理解强制类型转换?
在将''123456''转换为整型时需要自己写函数或者使用相应的库函数,会改变内存中的数据。(真实的转化,并不等于强制类型转换)
强制类型转换并不改变内存中任一二进制位,只改变了我们如何去解释该二进制数字,没有改变内存中的数据。(强制类型转换的本质)
7.#define _CRT_SECURE_NO_WARNINGS要放在(头)文件的最前面或者采用#pragma warning(disable:4996) (后者可以不放在最前面)
8.使用getchar()函数时要注意输入缓冲区的存在,getchar()先检索输入缓冲区中有没有字符,如果没有,才会把输入的字符加载进去,尤其注意我们通常输入的间隔符可能就会因为我们代码的不严谨而被加载到缓冲区中。
9.正数的源反补相同,负数的源反补按照上面步骤进行转换!
10.在未声明时不能用其它.c文件里的全局变量,如果要使用,需要用extern(关键字,专门用来声明外部符号的,用法为 extern 类型 变量名,全局变量的作用域是整个工程,生命周期是在程序的运行期间,而static修饰了全局变量后,就使全局变量只能在定义的文件内使用,即使在其它文件中用extern声明后也不能使用)进行声明。
一个全局变量在整个工程的其它文件内部中可以被使用是因为全局变量具有外部链接属性,当一个全局变量被static修饰后,这个变量的外部链接属性就变为了外部链接属性就变成了内部链接属性,使得这个全局变量只能在自己的源文件内使用,其它文件不能使用,给人感觉作用域变小了,生命周期没有变化,存储位置也没有发生变化。
11.内存空间的单位是字节。
12.键盘输入的内容,或者往显示器中打印的内容,全部都是字符,从printf()的返回值即可得出,因为printf的返回值就是输出字符的数目。就像getchar()输入1234,就可以通过printf()进行输出后得到1234,事实上,1234是四个字符。
无论是scanf还是getchar,输入都是以字符形式进行的,不同的是,scanf会进行格式化存储。所以我们把显示器或者键盘叫做字符设备,因为输入的是字符,输出的还是字符。
13.任何C程序,在默认编译好之后,运行时,都会打开三种输入输出流:
stdin:标准输入 FILE* stdin 键盘
stdout:标准输出 FILE* stdout 显示器
stderr:标准错误 FILE*stderr 显示器
14.计算机中的释放空间到底是指的什么?
首先,删除数据并不是把所有的数据清0/1。因为无论是清0还是清1,都是一个写入的过程,如果是清0/1的话,写入和清空应该花费同样多的时间。
计算机中清空数据,只要设置该数据无效即可
15.了解编译和链接。
(1)什么是编译和链接?
编译是为了将函数变量等变成,o二进制的机器码格式,链接是为了将各个独立分开的二进制的函数链接起来形成一个整体的二进制可执行文件。
(2)编译和链接以什么为单位?
编译以文件为单位、链接以工程为单位。
编译器编译时会将所有源文件依次读出来,以每个文件为单位进行编译,因此编译不会考虑其他的文件,显然这样就简化了编译器的设计。
链接的时候实际上是把第一步编译生成的.o文件作为输入,然后将它们链接成一个一个可执行程序,第一步有多少.c文件,编译时就会有多少个.o文件,链接后多个.o文件就会变成一个可执行文件。
(3)三种链接属性:外链接、内链接、无链接
外链接:外链接就是需要的函数与变量可以在外部文件找到,通俗说就是可以被跨文件访问。
内链接:与外链接相反,需要的函数和变量在当前的文件的内部就可以找到,或者说具有内部链接属性的变量只能在文件内部被访问,static修饰全局变量和函数都是内链接的。
无链接:这个符号本身不参与链接,它跟链接没有关系,局部变量(auto、和被static修饰的局部变量)都是无链接的。
(4)函数和全局变量的命名冲突问题
extern修饰的全局函数和全局变量都是外链接的,这些函数和变量在整个程序的所有.c文件中都是可以被访问到的,因此对于外部链接的全局函数和全局变量来说,避免命名冲突是非常重要的,特别是在一个大型的工程项目中,不出现相同的名字是很难做到的。所以在C++中给出了解决方案,就是使用命名空间namespace的方式,通俗点就是给一个变量带上各个级别的前缀,不过C语言中并没有这种方法。但是C语言也有自己的解决方案,就是使用之前的外链接、内链接和无链接这三种属性的方法。
C语言的解决方法是这样的,我们将明显不会再其它C文件中引用的全局变量/函数,使用static修饰使其成为内链接,这样在将来链接时,即使2个.c文件有重名的全局函数/变量,只要其中一个或两个为内链接就不会冲突。当然这种解决方案在一定程度上解决了这个问题,但是并没有从根本上解决问题,因此留下了一些瑕疵,今后我们在用C语言写大型项目时要格外注意命名问题。
(5)运用上面的知识分析运用static修饰全局变量和全局函数
当我们使用static修饰全局变量和全局函数的时候,他们的作用范围就被锁在了本文件内,其它文件在链接时无法使用这些函数和全局变量,这就是由原来的外链接属性变成了内链接属性,同时有限避免了函数和全局变量的命名冲突问题。
16.定义域和生命周期的概念
作用域概念:指的是该变量的可以被正常访问的代码区域(区域性的概念)
生命周期的概念:变量的创建到变量的销毁之间的一个时间段(时间上的概念)
17. 1. 全局变量,是可以跨文件,被访问的。 2. 全局函数,是可以跨文件,被访问的。
18.源反补的转换方法
原码:直接将二进制按照正负数的形式翻译成二进制就可以。 反码:将原码的符号位不变,其他位依次按位取反就可以得到了。 补码:反码+1就得到补码。
19.在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理; 同 时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码与原码相互转换,其运算过程是相同的,不 需要额外的硬件电路。计算也是用补码进行计算的!
20.printf()和scanf()在底层上也是由getchar和putchar实现的!
getchar函数是逐个字符进行读入,逐个字符进行输出的!如果进行相应的判定,自然也是逐个字符进行判定!下面例子即可证明。
21.在 16 位环境下,short 的长度为 2 个字节,int 也为 2 个字节,long 为 4 个字节。 16 位环境多用于单片机和低级嵌入式系统,在PC和服务器上已经见不到了。 对于 32 位的 Windows、Linux 和 Mac OS,short 的长度为 2 个字节,int 为 4 个字节,long 也为 4 个字节。
无论在32位还是64位的操作系统下。int 始终占据4个字节。
22.C语言语句可分为以下五类:
(1)表达式语句(例如 y=x+3;假设变量y和x均已定义)
(2)函数调用语句(MAX(x,y);假设函数MAX()已经定义)
(3)控制语句(if 、switch等等)
(4)复合语句(把多种语句复合在一起形成的语句)
(5)空语句(分号本身就可以作为一条语句,称为空语句 )
无论上述哪一种语句,都必须以分号结束。
23.有关于getchar和putchar
getchar的作用:
从一个流里读取一个字符,或者从标准输入(键盘)里面获取一个字符。
getchar的返回类型:int(存储文件结束标志-1)
putchar的作用:
写一个字符到流里面或者到标准输出(显示器)中。
putchar的输出类型:字符型
注意:在使用getchar时,如果前面输入了空格到了缓冲区中,不要忘了用getchar吸收缓冲区中的数据!
ctrl+z就是文件结束标志,使getchar终止
24.C程序地址空间:
程序在编译之前,main()就已经先预定好了分配的空间,即先开辟了空间,在main函数中定义的变量,都是在这段空间中重新申请空间来存放的,即二次开辟。我们把给函数预先分配的这块空间叫做栈帧。
这些函数开辟的那块空间也叫栈帧。在其它局部函数中定义变量也是在相应的栈帧上开辟的。
注意:调用函数时相应的栈帧开辟,调用结束后函数返回,释放栈帧。
#include
#include
char* show()
{
char str[] = "hello bit";
return str;
}
int main()
{
char* s = show();
printf("%sn", s);
system("pause");
return 0;
}
释放栈帧之后,里面保存的数据并没有被清除掉,因为计算机意义上的删除只是使数据无效化,无效化指的是这段内存空间是可被覆盖的,换言之,栈帧释放掉后,内存中仍然存储着之前释放栈帧中相应的数据,数据并没有随着栈帧的释放而被清空,在这个函数中show()函数返回的时候,str仍然指向"hello world"这个字符串。
通过监视函数可以看到,在调用printf()函数之前,字符串"hello world"还存在于内存中,当调用完printf()函数之后,字符串"hello world"在内存中就不存在了。前者的原因是计算机并不清空数据,虽然这些数据无效了,但我们依旧可以看到(因为它们仍然存在于内存空间中),后者的原因是因为printf()也是函数,也遵循上面的规则,调用printf()形成新的栈帧,调用结束,释放新的栈帧,新的栈帧会覆盖旧的栈帧结构,进行覆盖之后,原先的hello world字符串自然就不存在了,或者说是被改变了,当然,因为前面旧的栈帧被释放了,即无效了,所以才能被覆盖。
虽然编译器在编译的时候,不会真正的调用我们定义的函数,但编译器会根据我们定义的变量(类型数量),来预估出我们需要的空间大小,进而根据空间大小来开辟相应的栈帧,这也是为什么我们能够通过sizeof()关键字来求某某一变量的大小,因为在编译器在预估空间大小的时候就用到了sizeof()关键字来预估变量的大小。,当然,在这个过程中并没有真正的开辟空间,只有在调用的时候才会在真正的开辟相应的栈帧。
递归的过程实际上就是不断开辟栈帧的过程,如果超过预估的空间大小就会形成所谓的栈溢出的现象。
为什么临时变量具有临时性?
绝大部分变量,都是在栈上开辟的,即在函数内定义的,函数调用,形成栈帧,函数调用结束,栈帧被释放,而通过栈帧结构赖以生存的临时变量自然也就无法继续存在了,也应该被释放掉,即可以被覆盖掉了。总结来说,就是栈帧结构在函数调用完毕后,需要被释放。形象来说,就是皮之不存,毛将焉附?
return 语句不可返回指向占栈内存的指针,因为该内存在函数体结束时将自动被销毁。
问题:return后面的值是如何返回的?
return 返回数据的时候,先将对应的数据放在寄存器中,然后再将寄存器中的数据放到要保存的栈帧的对应变量的空间中,即函数的返回值,通过寄存器的方式,返回给函数调用方,这个过程,大多时候是运用的eax通用寄存器。
问题:如果不接收return返回的数据们这算数据将存放在哪呢?
仍然会放在寄存器中,只不过不再进行接收操作,即将数据返回到调用方。
25.int *a,b;这样写的时候,前面的a是指针类型,后面的b是整型数据类型
当然,我们也可以这样写:int *a=NULL,b = 0;我们并不推荐上述这两种写法,推荐的是下面这种写法:
int *a = NULL;
int b = 0;
(5)运算符
1.取模运算符%只能用于整数,即两侧只能是整数。
2.运算符优先级
3.&运算符取的永远是最低的那个地址。在C语言中,任何变量&都是最低地址开始。
4.在类型相同的情况下,对指针进行解引用,表示的就是指针所指向的目标。
int a = 10;
int *p = &a;
*p = 20;/
/
/
/ i; //正确
char* s = "abcdefgh //hijklmn"; //正确
//Is it a
valid comment? //正确
int j; //报错
system("pause");
return 0;
}
注意:注释被替换是在预处理阶段实现的,注释被替换,本质是替换成空格,上述报错的那一句本质上应该是 in t i;编译器自然会报错,报错是在预处理阶段进行语法检查时出错的,出现了语法错误。
(2)
#defineIDreplacementlist
//这段代码指的就是用replacement list替换ID
intabcdefgreplacementlist
上述两段代码都能编译通过,这说明# 和 define之间可以带空格。
//是C++风格的注释,而则是C语言风格的注释,前者可以一次写多个,不过从第一个//往后就都是注释的内容,
(3)
注意:不能嵌套注释。进行匹配。例如:
*/
最终第一行和第三行中的*/进行匹配,剩下最后一个*/。
(4)
注意下面这段代码
int x = 10 ;
int y = 10;
int z = 5;
int *p = &z;
y = x/*p;
这种代码一定要注意,/*容易被编译器认为是注释,所以会报错。
解决方案有两种:
1.y = x / *p;即在/后面加一个空格,不要让/*连在一起。
2.y = x/(*p);(推荐用这一种)。
(5)条件编译
1.
#include
#include
#define MonEY 1(只有定义了前面的宏,ifdef到endif中间的这段代码才能够正常运行,未定义则跳过)
int main()
{
#ifdef MonEY
printf("for test1n"); //test1
printf("for test2n"); //test2
#endif
system("pause");
return 0;
}
2.也可以通过if(0)来进行注释,但并不推荐,严重不推荐。
10、动态内存管理
11、文件操作
12、程序的编译(预处理)
1.int 和 long(int)一般都是八个字节,事实上对于long (int)的字节长度要求是大于或者等于int类型所占的字节数。
2.强制类型转换的格式是(类型) 变量名而不是 类型 (变量名),后者是ui变量的定义,编译器会显示对变量的重定义。
3.任何有值特性的均能用sizeof()求其所占的内存的大小,比如sizeof(10),编译器一般会把整数默认为是int 类型占据4个字节,把小数默认为是double占据8个字节。
1.C语言中是没有次方运算的,如果要进行次方运算需要运用pow()函数,^是异或运算符。
scanf格式输入要注意同步,scanf()运用时格式时非常严格的,代码格式和输入格式要严格对照。同时注意一点,如果定义普通变量之后未初始化是无法输出的,但如果在定义之后,用scanf()进行输入操作此时不初始化也没有问题,但我们通常并不建议这样操作,因为在编写程序时,我们赋的初值有时会具有某些含义,所以无论后续是否用scanf进行输入,最好都要初始化。
2.在定义变量时,因为编译器默认为我们输入的整数是int,输入的浮点数为double,所以在定义单精度浮点数时float a = 3.14f
3.浮点数在进行比较的时候,绝对不能用==进行直接比较,浮点数在存储的时候本身会有精度损失,进而导致结果可能会有各种细微的差别。
解决方案:#define EPSILON(精度的意思) 0.00001(自己设定的精度)
if ( (x - y) >-0.00001 && (x-y) < 0.00001) 或者 if(fabs(x-y)
实际上,系统已经给我们定义好了精度,即DBL_EPSILON 用法同上,判断两个数是否相等时是if(fabs(x-y)),如果判断某一个浮点数是否等于0时可用if(fabs(x)前面不能加=,即这个数不等于0)
4.区分0、 、NULL ‘0按照整数进行输出后的结果是48(0的ascii码值)
事实上,运用printf进行输出时,格式为%d时数据都是一样的,均为0,但它们的类型是不同的。
int a = 0; int *a = NULL(类型为void *); (能够被操作符(+-*/=等)两端连接起来的数据类型必须是一样的,如果不一样会发生报错或者警告)
int *p=NULL;
推荐if(NULL == p),而不推荐if(p==0)和if(p)和if(p==NULL)
5.按%p格式进行打印是用来打印地址的。
6.如何理解强制类型转换?
在将''123456''转换为整型时需要自己写函数或者使用相应的库函数,会改变内存中的数据。(真实的转化,并不等于强制类型转换)
强制类型转换并不改变内存中任一二进制位,只改变了我们如何去解释该二进制数字,没有改变内存中的数据。(强制类型转换的本质)
7.#define _CRT_SECURE_NO_WARNINGS要放在(头)文件的最前面或者采用#pragma warning(disable:4996) (后者可以不放在最前面)
8.使用getchar()函数时要注意输入缓冲区的存在,getchar()先检索输入缓冲区中有没有字符,如果没有,才会把输入的字符加载进去,尤其注意我们通常输入的间隔符可能就会因为我们代码的不严谨而被加载到缓冲区中。
9.正数的源反补相同,负数的源反补按照上面步骤进行转换!
10.在未声明时不能用其它.c文件里的全局变量,如果要使用,需要用extern(关键字,专门用来声明外部符号的,用法为 extern 类型 变量名,全局变量的作用域是整个工程,生命周期是在程序的运行期间,而static修饰了全局变量后,就使全局变量只能在定义的文件内使用,即使在其它文件中用extern声明后也不能使用)进行声明。
一个全局变量在整个工程的其它文件内部中可以被使用是因为全局变量具有外部链接属性,当一个全局变量被static修饰后,这个变量的外部链接属性就变为了外部链接属性就变成了内部链接属性,使得这个全局变量只能在自己的源文件内使用,其它文件不能使用,给人感觉作用域变小了,生命周期没有变化,存储位置也没有发生变化。
11.内存空间的单位是字节。
12.键盘输入的内容,或者往显示器中打印的内容,全部都是字符,从printf()的返回值即可得出,因为printf的返回值就是输出字符的数目。就像getchar()输入1234,就可以通过printf()进行输出后得到1234,事实上,1234是四个字符。
无论是scanf还是getchar,输入都是以字符形式进行的,不同的是,scanf会进行格式化存储。所以我们把显示器或者键盘叫做字符设备,因为输入的是字符,输出的还是字符。
13.任何C程序,在默认编译好之后,运行时,都会打开三种输入输出流:
stdin:标准输入 FILE* stdin 键盘
stdout:标准输出 FILE* stdout 显示器
stderr:标准错误 FILE*stderr 显示器
14.计算机中的释放空间到底是指的什么?
首先,删除数据并不是把所有的数据清0/1。因为无论是清0还是清1,都是一个写入的过程,如果是清0/1的话,写入和清空应该花费同样多的时间。
计算机中清空数据,只要设置该数据无效即可
15.了解编译和链接。
(1)什么是编译和链接?
编译是为了将函数变量等变成,o二进制的机器码格式,链接是为了将各个独立分开的二进制的函数链接起来形成一个整体的二进制可执行文件。
(2)编译和链接以什么为单位?
编译以文件为单位、链接以工程为单位。
编译器编译时会将所有源文件依次读出来,以每个文件为单位进行编译,因此编译不会考虑其他的文件,显然这样就简化了编译器的设计。
链接的时候实际上是把第一步编译生成的.o文件作为输入,然后将它们链接成一个一个可执行程序,第一步有多少.c文件,编译时就会有多少个.o文件,链接后多个.o文件就会变成一个可执行文件。
(3)三种链接属性:外链接、内链接、无链接
外链接:外链接就是需要的函数与变量可以在外部文件找到,通俗说就是可以被跨文件访问。
内链接:与外链接相反,需要的函数和变量在当前的文件的内部就可以找到,或者说具有内部链接属性的变量只能在文件内部被访问,static修饰全局变量和函数都是内链接的。
无链接:这个符号本身不参与链接,它跟链接没有关系,局部变量(auto、和被static修饰的局部变量)都是无链接的。
(4)函数和全局变量的命名冲突问题
extern修饰的全局函数和全局变量都是外链接的,这些函数和变量在整个程序的所有.c文件中都是可以被访问到的,因此对于外部链接的全局函数和全局变量来说,避免命名冲突是非常重要的,特别是在一个大型的工程项目中,不出现相同的名字是很难做到的。所以在C++中给出了解决方案,就是使用命名空间namespace的方式,通俗点就是给一个变量带上各个级别的前缀,不过C语言中并没有这种方法。但是C语言也有自己的解决方案,就是使用之前的外链接、内链接和无链接这三种属性的方法。
C语言的解决方法是这样的,我们将明显不会再其它C文件中引用的全局变量/函数,使用static修饰使其成为内链接,这样在将来链接时,即使2个.c文件有重名的全局函数/变量,只要其中一个或两个为内链接就不会冲突。当然这种解决方案在一定程度上解决了这个问题,但是并没有从根本上解决问题,因此留下了一些瑕疵,今后我们在用C语言写大型项目时要格外注意命名问题。
(5)运用上面的知识分析运用static修饰全局变量和全局函数
当我们使用static修饰全局变量和全局函数的时候,他们的作用范围就被锁在了本文件内,其它文件在链接时无法使用这些函数和全局变量,这就是由原来的外链接属性变成了内链接属性,同时有限避免了函数和全局变量的命名冲突问题。
16.定义域和生命周期的概念
作用域概念:指的是该变量的可以被正常访问的代码区域(区域性的概念)
生命周期的概念:变量的创建到变量的销毁之间的一个时间段(时间上的概念)
17. 1. 全局变量,是可以跨文件,被访问的。 2. 全局函数,是可以跨文件,被访问的。
18.源反补的转换方法
原码:直接将二进制按照正负数的形式翻译成二进制就可以。 反码:将原码的符号位不变,其他位依次按位取反就可以得到了。 补码:反码+1就得到补码。
19.在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理; 同 时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码与原码相互转换,其运算过程是相同的,不 需要额外的硬件电路。计算也是用补码进行计算的!
20.printf()和scanf()在底层上也是由getchar和putchar实现的!
getchar函数是逐个字符进行读入,逐个字符进行输出的!如果进行相应的判定,自然也是逐个字符进行判定!下面例子即可证明。
21.在 16 位环境下,short 的长度为 2 个字节,int 也为 2 个字节,long 为 4 个字节。 16 位环境多用于单片机和低级嵌入式系统,在PC和服务器上已经见不到了。 对于 32 位的 Windows、Linux 和 Mac OS,short 的长度为 2 个字节,int 为 4 个字节,long 也为 4 个字节。
无论在32位还是64位的操作系统下。int 始终占据4个字节。
22.C语言语句可分为以下五类:
(1)表达式语句(例如 y=x+3;假设变量y和x均已定义)
(2)函数调用语句(MAX(x,y);假设函数MAX()已经定义)
(3)控制语句(if 、switch等等)
(4)复合语句(把多种语句复合在一起形成的语句)
(5)空语句(分号本身就可以作为一条语句,称为空语句 )
无论上述哪一种语句,都必须以分号结束。
23.有关于getchar和putchar
getchar的作用:
从一个流里读取一个字符,或者从标准输入(键盘)里面获取一个字符。
getchar的返回类型:int(存储文件结束标志-1)
putchar的作用:
写一个字符到流里面或者到标准输出(显示器)中。
putchar的输出类型:字符型
注意:在使用getchar时,如果前面输入了空格到了缓冲区中,不要忘了用getchar吸收缓冲区中的数据!
ctrl+z就是文件结束标志,使getchar终止
24.C程序地址空间:
程序在编译之前,main()就已经先预定好了分配的空间,即先开辟了空间,在main函数中定义的变量,都是在这段空间中重新申请空间来存放的,即二次开辟。我们把给函数预先分配的这块空间叫做栈帧。
这些函数开辟的那块空间也叫栈帧。在其它局部函数中定义变量也是在相应的栈帧上开辟的。
注意:调用函数时相应的栈帧开辟,调用结束后函数返回,释放栈帧。
#include#include char* show() { char str[] = "hello bit"; return str; } int main() { char* s = show(); printf("%sn", s); system("pause"); return 0; } 释放栈帧之后,里面保存的数据并没有被清除掉,因为计算机意义上的删除只是使数据无效化,无效化指的是这段内存空间是可被覆盖的,换言之,栈帧释放掉后,内存中仍然存储着之前释放栈帧中相应的数据,数据并没有随着栈帧的释放而被清空,在这个函数中show()函数返回的时候,str仍然指向"hello world"这个字符串。
通过监视函数可以看到,在调用printf()函数之前,字符串"hello world"还存在于内存中,当调用完printf()函数之后,字符串"hello world"在内存中就不存在了。前者的原因是计算机并不清空数据,虽然这些数据无效了,但我们依旧可以看到(因为它们仍然存在于内存空间中),后者的原因是因为printf()也是函数,也遵循上面的规则,调用printf()形成新的栈帧,调用结束,释放新的栈帧,新的栈帧会覆盖旧的栈帧结构,进行覆盖之后,原先的hello world字符串自然就不存在了,或者说是被改变了,当然,因为前面旧的栈帧被释放了,即无效了,所以才能被覆盖。
虽然编译器在编译的时候,不会真正的调用我们定义的函数,但编译器会根据我们定义的变量(类型数量),来预估出我们需要的空间大小,进而根据空间大小来开辟相应的栈帧,这也是为什么我们能够通过sizeof()关键字来求某某一变量的大小,因为在编译器在预估空间大小的时候就用到了sizeof()关键字来预估变量的大小。,当然,在这个过程中并没有真正的开辟空间,只有在调用的时候才会在真正的开辟相应的栈帧。
递归的过程实际上就是不断开辟栈帧的过程,如果超过预估的空间大小就会形成所谓的栈溢出的现象。
为什么临时变量具有临时性?
绝大部分变量,都是在栈上开辟的,即在函数内定义的,函数调用,形成栈帧,函数调用结束,栈帧被释放,而通过栈帧结构赖以生存的临时变量自然也就无法继续存在了,也应该被释放掉,即可以被覆盖掉了。总结来说,就是栈帧结构在函数调用完毕后,需要被释放。形象来说,就是皮之不存,毛将焉附?
return 语句不可返回指向占栈内存的指针,因为该内存在函数体结束时将自动被销毁。
问题:return后面的值是如何返回的?
return 返回数据的时候,先将对应的数据放在寄存器中,然后再将寄存器中的数据放到要保存的栈帧的对应变量的空间中,即函数的返回值,通过寄存器的方式,返回给函数调用方,这个过程,大多时候是运用的eax通用寄存器。
问题:如果不接收return返回的数据们这算数据将存放在哪呢?
仍然会放在寄存器中,只不过不再进行接收操作,即将数据返回到调用方。
25.int *a,b;这样写的时候,前面的a是指针类型,后面的b是整型数据类型
当然,我们也可以这样写:int *a=NULL,b = 0;我们并不推荐上述这两种写法,推荐的是下面这种写法:
int *a = NULL;
int b = 0;
(5)运算符
1.取模运算符%只能用于整数,即两侧只能是整数。
2.运算符优先级
3.&运算符取的永远是最低的那个地址。在C语言中,任何变量&都是最低地址开始。
4.在类型相同的情况下,对指针进行解引用,表示的就是指针所指向的目标。
int a = 10;
int *p = &a;
*p = 20;/
/
/
/ i; //正确
char* s = "abcdefgh //hijklmn"; //正确
//Is it a
valid comment? //正确
int j; //报错
system("pause");
return 0;
}
注意:注释被替换是在预处理阶段实现的,注释被替换,本质是替换成空格,上述报错的那一句本质上应该是 in t i;编译器自然会报错,报错是在预处理阶段进行语法检查时出错的,出现了语法错误。
(2)
#defineIDreplacementlist
//这段代码指的就是用replacement list替换ID
intabcdefgreplacementlist
上述两段代码都能编译通过,这说明# 和 define之间可以带空格。
//是C++风格的注释,而则是C语言风格的注释,前者可以一次写多个,不过从第一个//往后就都是注释的内容,
(3)
注意:不能嵌套注释。进行匹配。例如:
*/
最终第一行和第三行中的*/进行匹配,剩下最后一个*/。
(4)
注意下面这段代码
int x = 10 ;
int y = 10;
int z = 5;
int *p = &z;
y = x/*p;
这种代码一定要注意,/*容易被编译器认为是注释,所以会报错。
解决方案有两种:
1.y = x / *p;即在/后面加一个空格,不要让/*连在一起。
2.y = x/(*p);(推荐用这一种)。
(5)条件编译
1.
#include
#include
#define MonEY 1(只有定义了前面的宏,ifdef到endif中间的这段代码才能够正常运行,未定义则跳过)
int main()
{
#ifdef MonEY
printf("for test1n"); //test1
printf("for test2n"); //test2
#endif
system("pause");
return 0;
}
2.也可以通过if(0)来进行注释,但并不推荐,严重不推荐。
10、动态内存管理
11、文件操作
12、程序的编译(预处理)
1.取模运算符%只能用于整数,即两侧只能是整数。
2.运算符优先级
3.&运算符取的永远是最低的那个地址。在C语言中,任何变量&都是最低地址开始。
4.在类型相同的情况下,对指针进行解引用,表示的就是指针所指向的目标。
int a = 10;
int *p = &a;
*p = 20;/
/
/
/ i; //正确
char* s = "abcdefgh //hijklmn"; //正确
//Is it a
valid comment? //正确
int j; //报错
system("pause");
return 0;
}
注意:注释被替换是在预处理阶段实现的,注释被替换,本质是替换成空格,上述报错的那一句本质上应该是 in t i;编译器自然会报错,报错是在预处理阶段进行语法检查时出错的,出现了语法错误。
(2)
#defineIDreplacementlist //这段代码指的就是用replacement list替换ID intabcdefgreplacementlist
上述两段代码都能编译通过,这说明# 和 define之间可以带空格。
//是C++风格的注释,而则是C语言风格的注释,前者可以一次写多个,不过从第一个//往后就都是注释的内容,
(3)
注意:不能嵌套注释。进行匹配。例如:
*/
最终第一行和第三行中的*/进行匹配,剩下最后一个*/。
(4)
注意下面这段代码
int x = 10 ; int y = 10; int z = 5; int *p = &z; y = x/*p;
这种代码一定要注意,/*容易被编译器认为是注释,所以会报错。
解决方案有两种:
1.y = x / *p;即在/后面加一个空格,不要让/*连在一起。
2.y = x/(*p);(推荐用这一种)。
(5)条件编译
1.
#include#include #define MonEY 1(只有定义了前面的宏,ifdef到endif中间的这段代码才能够正常运行,未定义则跳过) int main() { #ifdef MonEY printf("for test1n"); //test1 printf("for test2n"); //test2 #endif system("pause"); return 0; }
2.也可以通过if(0)来进行注释,但并不推荐,严重不推荐。



