C的的笔记

 忠告:学新而温故,你只有学了更深的内容才能理解前面不太明白的内容。
(比如你一开始接触arr是首元素地址,你不太理解,但当你学完指针
和内存的相关知识,你会发现你对arr的理解更上一层楼了。所以当时不会先放着,等学的更深入了在回头看之前不理解的内容).

面向过程:用于设计简单问题的如何一步一步实现。微观具体解决怎么实现。
面向过程对象:对一个复杂事务的宏观把握。如怎么造车(你很难想这个车怎么一步一步造出来,但这个车有什么,很好想。),设计的思想

所以写一个代码,既有面向过程的思想,又有面向对象的思想。两者相辅相成,面向对象,宏观把控,面向过程,微观实现。

p48 剑指offer 

p49 c语言没有字符串类型
p65,p66《高质量c/c++编程指南》 林锐  c陷阱和缺陷  c和指针
p77《程序员的自我修养》很经典特别好
温馨提示:看不懂不要硬看,可以歇一歇、查查资料。硬看影响心态
p81 《c语言深入解剖》
调试:1.用F9设置的断点处,断点处必须是一定可执行的,不要在可能执行的地方设置断点。
(如 如果你在if那设置断点,要是条件不满足,不一定会执行)
2.配合F5 使用,调到断点处
3.再按F11,去调试你想调试的地方。

当你给一个char类型的变量a直接赋整数值(不加’‘)时,其实以%c打印出来的是该整数对应的ASK码值。
如你给0,打印出来的就是\0,\0打印出来是什么都没有project44(因为0的ask码值就是'\0')
 
语句:
选择:
当你有多个选项要选择时,用选择语句。if使用方式:if elseif  elseif  else (最后)if语句不跟大括号,默认只跟一条语句。使用else 前面
必须要有if来配对else

2.三目:exp1 ?exp2:exp3  用于条件判断  表达式一成立吗,成立执行表达式2,否则执行表达式三。
何时用: 一个判断条件,二选一时用。

3 .switch case  : 1,switch()括号内的表达式得为整数值(char型为特殊的整形,因为里面存的是ASK码)
 注意一个case完了后要跟一个break,这样程序看到break,才会跳出去,不然会一直执行,直到遇到break

用途:不用条件判断,从很多选择中选一个。 case后面的写法:1.   case 1:  ;2.   case ‘a’:

2.循环:当你要一直重复干一件事时.循环三要素:起点,终点,变化(趋近循环条件)
 continue是继续循环前面的程序(往回走)。 一般和if在一起使用(  if(); continue;
当执行到break前面的条件满足时,提前结束循环,直接跳出循环。
.注意死循环的原因是你的判断条件恒成立。不死循环方法:1,循环()里的条件逐渐区域结束2,放变量,0结束,非零继续
for(赋初值(可以不止一个赋值句子,可以为零个、一个或多个变量设置初值执行);条件是否满足判断(也可以多个表达式但用&&这种逻辑符
来连接,不能用逗号表达式);在执行完循环体后再进行更新i的值(可以不止一个表达式如i++、j--))

逗号表达式:前面表达式计算完后,只看最后一个表示式。exp1,exp2,exp3
函数:
sqrt square root
fabs  absolute
幂函数:pow
指数函数:exp(x):etox

puts 自带\n  gets( arr ),arr表示get的string放到s这个数组里 以回车结束 

定义多个变量不能连=,必须用,隔开 int a,b,c;
(int)(express)强制类型转换

scanf是把你输入的东西赋值给你定义的变量

函数下的{}表示函数所控制的内容 如main控制{}里的内容

函数构成:   返回类型+函数名+(参数)

变量名只由数字、字母、_下划线组成

scanf,用doubl时,必须用%lf
print   double,不必用%lf,%f就行

计算机可以识别字符,但区分不开数字,如44.2 
变量取规则:第一个变量名必须是字母or下划线
输入型语句 scanf getchar
输出型语句 printf putchar

数据类型: char  1,可以用%d输出, 因为有对应的ASK2,2。用%c 
数组:在定义时,如果要指定多少个元素,【】内必须用常量
调用数组时,【】内可以填变量,不然就很难操作了。

strlen 是算\0之前的字符长度。
***注:字符串里就算是空串也有 '\0'  .它不算字符串里的内容,但占据“”串里的一块空间。作用是标识字符串的终点位置。
可以用该代码去理解‘\0'的意义。
    char arr[3] = "";
    printf("%c", arr[0]);

一些不知道该怎么分类的知识点:extern  
register 建议把我的变量放到寄存器 如 register a=1;
#define p452,宏,其实本质就是做替换工作,把参数的形式原模原样的进行替代。所以说是类函数

格式声明:由%和格式字符组成.如 %d 
%nd、%-nd n表示数据宽度  3.5.3  n大于0,右对齐,n小于0,左对齐。
  %m.nf   m表示数据宽度,n是小数的位数。
%e:以科学计数法打印。
e3:十的三次方  前面有数,后面必须是整数  如 3.2e3
%u (unsigned)无符号打印

字符:

\表示转义字符:
\t 空几个格  \n表示换行后面的句子  float用 \f  打印double用  %lf   %e是以科学计数法显示
除和注释用/  转义用\。\表示转义字符, \ \第一个\ 把第二个\给转义
\xddd表示16进制对应的ASK字符 
\ddd ,三个d作为整体,表示一个八进制值,表示八进制对应的值 在ASK上对应的字符

数字or字母 在‘ ’内都表示字符,在ASK里有对应的值,如'1'的ASK并不是1,是49 'a'与'A'的差32  ’a‘  97‘A‘ 65

变量类型必须是char*时,才能打印字符串,且用%s。而且\0是字符串结束标志,要牢记\0在字符串中的作用
1.当你strlen是,遇到\0它就不往后数了,即使后面有值。
2.当printf遇到\0   \0后面的内容它就不print了 如“adf\0safd”你如果print,只能输出adf。
预处理指令 : 1.#define pi 3.14 define的用法是把 A 定义为 B,所以 max 100;的意思是 max等价100;  所以最好在B后别加封号

运算符:

整数以都补码的形式存在

操作运算符:
a=exp2   把exp2赋值给左边,只有左边是单个变量才能存储exp2
j=++i,+在前,所以先加,运算前就已近加上了 后置++,j=i++i在前,先赋值,i再++;如 *p++=i  先把i赋值给指针p,然后地址p++ 

位操作:左移、右移,缺的位补零 
~按二进制位取反,然后再以补码形式存储,要打印出来给你看就变回原码 如对0 进行~,最后就是-1(p4)

位操作符:按位与&、按位或|、异或^(对应的二进制位,相异为1,相同为零,也就是他喜欢“异”的,)
对应的两个数的位进行比较。与(得都真才为真)


逻辑操作符:&&  ||(vs编译器真用1,假用0)
表达式运算规则:1,优先级高的先算2,优先级相同时,按结合方向     
p2.运算操作符、移位操作符(左移、右移)
p8:二分查找法

p9 memset ‘1’任何字符在计算机中,存的都是相对应的ASK码值 。所以‘w'存的是ask码值。
当你用选择语句,且只有两个选择时,除了用if else 还可以用  exp ? a:b 

p10
形参:函数()内的参数叫形参
 形参是实参的临时拷贝,其实就是说,实参参过去后,形参实例化,形参自己开辟了一块新的空间。所以修改形参的值,并不会影响实参的值。
arr【】传参传的是arr的地址。因为如果把整个数组传过去,空间浪费太严重。所以如果你要得到数组元素个数时,得先在main函数内计算好,
然后把sz传过去。      

函数的嵌套调用(该函数嵌套其他函数),和链式访问(该函数的返回值,作为其他函数的参数)

函数分三块:1函数的声明2,函数的使用3,函数的定义
函数真正的使用:函数声明 创一个add。h文件  函数调用  在 源。c 文件  函数定义  在add。c文件

引用库函数的用#include< > 但引用自己的函数,用#inlude “  ”用双引号

递归:
自己调用自己。
特点:
1,当你要重复做事,重复的事情的算法一样时(就像一整个大的巧克力块,拆一次,少一半),可以考虑递归
2,每次深入到不能深入(所以就像循环一样,需要合适条件),然后触到最深处反弹到当时的入口,很具有重复性的反弹。
3.每次递归都需要该循环不断接近条件,不然就死循环了

用途:把重复的大事,利用自己调用自己,不断拆成一个比一个小的零件。

老师说,递归本质是子问题。

怎么写:递归的本质就是拆和自己算法相同的大块头。算法相同,按顺序一个一个往下拆解,且是逐层往下。你写的条件是递归结束条件,
也就是执行到你的条件时,函数已经执行到最底层了。


p11 实参 要真实传给函数的参数;形参,函数括号内的参数,只有当要调用函数时,才会开辟内存空间(实例化).

p12递归注意,有时候没有死循环,你递归也会栈溢出,可能是你调用次数太多,把栈给用完了
p13【】用于访问数组的元素。  【】里面必须是常量,不能是变量。如【a】不行。数组初始化方法:1。{}2。“”(用于字符串)。
二维数组:特点:1.当不加{},逐行填充。2.可以省略行,但一定不能省略列。因为列清楚了,逐行填充的时候就知道到哪列换行。但如果只知道
行数,按行填充时就不知道填到哪里结束。就是说不知道每行的边界在哪。3.二维数组在内存中也是连续存储。
4.因为连续存储,所以arr(二维数组)可以当作首行地址相当于arr【1】表首行。   

除了 1,sizeof(arr)2,&arr表示整个数组;其他地方出现的arr表示首元素地址。

p14循环一次,重新为变量刷新一次值,因为程序是按顺序执行的,循环一次,按顺序刷新一次。如冒泡排序里的flag。project40/45:冒泡
p15 解决VS的scanf未定义
p16 srand 的意思应该时start random 随机数的起点。
project 43 三子棋
p19:操作符  记住!!!所有整数的值在内存里都是以补码的形式存储。          教你如何看内存
 做除法若想得到小数部分,除了要写成float或double类型外,还要把至少其中一个操作数带小数部分,不然无法得到小数部分,如 5/2.0
%取模时,两个操作数必须是整数,得不带小数,不然会报错。如5%2.0会报错

>>右移操作符,移的是二进制位。如 a>>1 表示a右移一位  左移操作符:<< 左边丢弃,右边补0
移位操作符分两种 1,算术右移,右边移的丢弃,左边补原来的符号位。2,逻辑右移,右边丢弃,左边粗暴补0
注:对于移的位不能出现移负数位 如a<<-1,这样编译器看不懂。

按位与&  a&b
按位或 |    a|b
按位异或^  相异为1,相同为0

对一个数不断 %和/ 可以不断得到他一位位的数 

! 的用法:如果你认为 a为真 ,那么!a表示假  一般用于选择语句,如果!a 执行什么什么操作。
正数的原反补相同 

~按位取反操作符  你如果要打印~a  默认~a的值在计算机里是以补码的形式存储所以要执行-1、取反得到原码的操作。

野指针(你的地址指向了不属于你的空间 )1.指针若不初始化,指针为随机值2,指针越界
注:我没有开辟就不是我的空间!!!,如果你访问,就是非法访问

p20
sizeof() ()里如有表达式,该表达式不进行计算。 如sizeof(s=a+a) 该表达式不进行计算
前置++, 先++ 后使用
后置++,先使用,后++

逻辑操作符:
&&   | |   用于判断表达式之间的关系。 和关系操作符作用差不多,

&&逻辑与特点:  自左向右结合,如果遇到假的,后面的就不看、不算了
||   只要遇到一个真,后面就不看了

exp1?exp2  :  exp3 相当于if else

逗号表达式:a=exp1,exp2,exp3   从左向右依次执行,把最后表达式的值赋给a。

结构体指针->成员名
结构体变量.成员名

整型提升:
提升对象:1.不足32位的数。如char short  2.   谁定义了 a,针对该定义的类型提升,如char a 提升的是有符号的a;p29     3.对补码提升
怎么提升:1.提升的数是变量的符号位(有符号位,补符号位的数,无符号数,直接补零)

如char a=0xb6;  把一个字节的a按照符号位提升到32位,此时是补码,
然后截断,把最后八个地位给a ,输出时,输出-1、取反后的原码 
 char c=a+b 同理。a、b先提升32位,然后截8位赋给c,最后以补码存起来,打印以原码。
注:任何我们看到的都是原码,计算机存的都是补码。只不过正数的原反补相同。

只要某个不满32位的数进行表达式运算(得是算术运算符,才能整型提升),都会有整型提升 如 char a;sizeof(+a)的值就是4

算术转换:
如果两个类型不一样的数据进行运算,把小的类型转换为和大的类型一样,然后进行运算 如float和int int会变成float,然后进行运算。
如果反过来,把大的类型转换为和小的类型一样,就会精度丢失。所以是把小的类型转换为和大的类型一样


p21
不同指针类型的区别和意义:1。类型决定该指针有权访问几个字节。
(如int* p 表示p虽是指向第一个字节,但解引用时,管控四个字节的范围。 char *p 表示p只有权管一个字节)。
2,决定了p+1能跨多少个字节

野指针:指针不初始化2.数组,arr这个指针越界访问。3,指向已经释放的内存空间

NULL = =(void*)0

p22

对*p++=0的理解 :
指针运算 
1,指针加减整数

2, 指针加减指针, 问题:为什么指针加减指针是元素个数?答:其实是具体字节数,只不过编译器除以相应的字节大小了,方便用户看
 如arr【9】-arr【0】 你print 出来是9,是因为编译器除以4了。

3.指针之间比较大小 

C标准规定可以越界访问最后一个元素的后一个地址,但不允许越界访问第一个元素前一个元素的地址。
其实所谓可以越界,就是我只指向你,但你那块空间我不用。

任何一个数据(整数、指针etc)都可以有变量来保存,且每个变量都有对应的地址
int int* int* *理解:
int *  该最右边的*,表示我是存别人数据地址的变量,int 表示我指向的数据是int
int * *   最右边的*表示我是指针,用来存别人地址;左边的int * 表示我指向的数据是int*

p23return 只能return一个表达式or数据     设计的函数尽量功能单一、独立        在{}内定义的局部变量只在{}内有效,出了{}就无效了了!!
全局变量,不初始化,默认是0            当有符号数,和无符号数比较时,有符号数会被污染为无符号数。

p24 当你要对二进制位进行操做时,可以用位操作符& | ^ 如计算某个数二进位中1的个数
求一个数的二进制位中1的个数算法 :1,和1按位与,循环32次  2, n=n&(n-1)每做一次,n最右边的1消失

p25
调试步骤
1.发现有错误
2。用隔离、消除等方式定位错误位置
3.发现错误原因
4.找到解决方案
5。重新测试

p26:结构体声明(创建某种类型的结构体)、定义变量(全局、局部变量)、传参(传值(传变量本身)、传址)
压栈的讲解

p27
调试:   注:得先按调试,才会出现监视、反汇编这些东西。
debug 调试版本,程序员可以主动调试代码
release发布版本,代码被优化了,但不能调试代码。
调试知识:
断点:让代码停下来的地点
F5,第一次F5表示启动调试,到断点处停下,第二次,停到执行逻辑上的下一个断点       而ctrl+F5表示开始执行,不调试
F9和F5配合使用:先用F9设置断点,然后F5让代码跑起来,就会跑到你设置的断点处。然后按F11就会逐语句执行代码
断点更精细的使用:右击断点,点击条件,你就可以输入你想在某处停下来的条件。如一个100次的循环,你输入i=6,他就会在i=6处停下
F10:逐过程, (如不关心调用的函数内部是怎么执行的,把一次函数调用当作一个过程
F11:逐语句,代码的每句话都执行。
且F11和F10可以混合使用,你不想看函数内部实现你就按F10,想看就按F11.
shift+F11 从函数内部跳出。

调试时,查看信息的方式:

监视:手动输入你想观察的值
自动窗口:自动的展示上下文环境的变量信息 缺点:不能手动输入你想观察的值
局部变量:展示局部变量的信息  缺点:不能手动输入你想观察的值
内存:查看内存里存放的数据和地址

反汇编:查看C语言对应的汇编代码
寄存器:
调用堆栈:    查看函数调用时的压栈顺序,如果你递归看不明白,可以打开调用堆栈。

栈区使用规则:先创建的变量放到高地址处,后创建的放在低地址处。所以在栈区创建的高地址,一定是先创建的变量  
所以先创建i,然后数组越界,就可能死循环

c语言不允许在for里面定义变量

strcpy:特点:把source里的包括\0也一起拷贝过去

 const在*左边,如int const* p = &a; 或者const char* p   =“abcd”,const修饰*p 即p指向的内容不能被修改
也就是利用 解引用去 赋值没啥用 如*p=‘M’失败
const在*右边    char* const p =“aa",  修饰的是指针p,即 指针不能被改。
const int * const p = &num; 第一个const: num既不能被改  第二个const:p又不能指向其他男朋友。

p28
不同的数据类型在内存中存储方式不同 如 float a=10.0 与int b=10;不同

整形家族: char(因为字符最终是以ASK码存储的) int short long  
signed int 有符号类型(最高位0、1表正负)unsigned int 无符号位

浮点家族:float double

构造类型 数组 结构体 枚举  联合

空类型 void 无返回  function(void)表示我不需要参数

无符号数 原反补相同
有符号数 :符号位 + 数值位     原反补的数值为各不相同
而有符号位的正数的原反补也相同,因为和无符号位长的一样(符号位为0)

整数在内存中,是以补码存的 。原因cpu只有加法器,用补码,加和减可以统一操作,都视为+

大端小端讲解: 大小端字节序指,二进制位在电脑中的
有大小端原因:存在怎么安排字节顺序的问题
指针是指向 在低地址处的第一个字节地址。 project62 p、p1

p29
char类型存储理解

数组是存放相同数据类型的存储空间 

在char 中 1000 0000 默认为-128 ,实际-128为 1 1000 0000,因为它必须要有符号位,所以牺牲了第二个1,来用1000 0000表示-128。

p30
整数在计算机里的计算:a+b,先把a、b的补码写出来,两个补码相加,然后以源码输出

p31:
浮点型在内存中的存储。 
针对float类型:按S、E、M的二进制顺序存储
1.E要+127这个中间值,确保E是unsigned int,
2.M小数点前必须是1,存的时候不存小数点前的有效数1
3.M位数不足23bit,后面补零。我知道为什么后面补零了,因为当你要显示这个浮点数时,直接是1.M的内容

当存储的E有0、有1正常算2。当E为全0,表示原来可能是-127次,分母特别大,所以规定还原时  M为0.几  E直接为-126次

p32     char用来定义单个字符   
 char*可以用来定义字符串首字符的地址。把字符串给p时,传的是首字符的地址。如char * p="abcd"    
所以字符串本质是首字符地址  而且  当你printf %s 时,他会从你传的p开始,依次往后打印字符
该字符串要用字符指针p接收,且该字符串不能被修改   因为该串字符存储在内存的只读数据区(字符常量区)

在定义的时候 *+变量名表示该变量是指针变量。所以int *p  表示p指向的是整形     在应用时,*+变量表示解引用,直接访问该地址内的元素
所以定义时,去掉名字就是类型

指针数组

p33
(20分钟)数组指针理解:无论什么变量,在定义的时候,去掉变量名,就是变量的类型。
如  int  (*parr1)[ 6 ],去掉名字后,就是int (*)【6】类型

数组名arr表示首元素地址、&arr表示整个数组地址,所以&arr+1跳过整个数组。
数组指针的应用

p34
 一维数组传参方式: 1,传数组,以arr【】传参,【】里的个数可以省略,没什么影响。 
        2.传指针,把arr看作首元素地址传 int *parr

二维数组传参:1.传数组  只能省略行,不能省略列,因为可以不知道有多少行,但必须知道一行有多少元素.
2.传指针  int (*parr)[5]
二维数组的数组名arr是 首行的地址得用数组指针接受
对数组地址 p 解引用,得到的是数组名arr,而数组名arr也是个指针,所以arr【】可以理解为  指针【】
arr【2】表示找下标为2的元素  所以 arr【i】等价于  *(p+i) 等价于 p【i】
 int(*arr【10】)【5】  因为【】优先级高于* 所以arr先和【】结合,  去掉arr【10】就得到类型 int(*)【5】,去掉名字,就是类型


函数指针:存放函数地址。表示为 &ADD 和 ADD 都可以 。  &+函数名 = = 函数名一样,都表示函数的地址 。
可以类比&arr、arr 但&arr表示整个数组的地址、arr表示首元素地址。 因为int (*p)(int,int)=ADD,p与ADD等价,
所以ADD(  )可以等价理解为  p+(   )。括号是函数调用操作符用来接收参数。
所以(*p)(  ,)与p(  , )都可以,*就是个摆设,无所谓有或没有

 int( *p)(int,int)  ( *p)表示这是指针。(     )表示函数。最前面的int表示返回类型

p35   

函数指针在typedef时与一般的 typtdef  unsigned int  uint 不一样,要把重命名的内容放在 *后面  
如 typedef  void (* pfun_t)(  int  )   把 void (*)(  int  ) 写成pfun_t,但pfun_t要写在*后面

返回类型的确定方法:
在定义时,把函数名和参数去掉,就是返回类型 如 int Add ( int, int ) -》 返回类型为 int

函数指针 的数组:每一个元素是函数指针,如 int(*  p[ 6 ]  )(   )~  可以理解为 int (*)arr【 3】
 int(* )( int,int)去掉数组后,就是数据类型, 返回类型是  int,参数为俩int的函数指针  拿到一个函数地址,就可以在地址后面加(),
如arr【1】(int,int) 以此来调用这个函数 
                      
该数组用途:转移表
用于存放多个函数长相相似的地址。如加减乘除,返回类型都是int 传递的参数也都为(int,int)方便调用不同的函数

指向(函数、指针)数组的指针:说白了就是数组指针,类似     int(*p)【 5】 指针指向五个元素,每个元素是int。

     函数 数组指针int (   3.*  1.(*pp)  2.【】) )( int,int)  1,是指针2,指向数组3,数组每个元素是函数指针

p36:
去掉数组名+【】就是元素类型 char(* 我被去掉了 )(char*,const char*)
函数指针数组:好处:存长相类似的一群函数指针,当要用函数时 ,利用下标找到指针,再加()就可以调用函数了。如   parr【1】(2,4)
所以这又叫转移表,利用数组元素,直接转移到函数那去
   
回调函数:在main函数中,调用test(),在test内,利用函数指针调用print函数 把print函数调回到test( 函数指针 )内,
print就叫做回调函数。我main里没主动调用你,你是在test里被调用的。  回调函数是通过函数指针调用的函数

p37

void,空,void*(万能桶),无类型的指针,表示可以存储任何类型的指针,如int a =1 , void* p=&a 
用途:在不知道要传什么指针过来时,用void*接收

指针类型决定了解引用后我能访问几个字节 ,因为是void,不知道该访问几个字节,所以void* 类型的指针不能解引用、不能加减整数,

qsort函数(quick sort)用于对各种类型数组的排序,qsort需要参数:array ,number,size ,比较函数(哪两个进行比较)的指针
其中比较函数的参数是指向元素的俩指针
要传数组起始位置,元素个数,每个元素大小,compare function(比较函数要自己写)的地址(函数名就可以直接当地址)
结构体指针后加→,用来表示该指针指向的是该结构里的哪个类型变量(比如是char还是int?)

比较字符串大小不能用<> 而应该用strcmp函数比较,strcmp比较的是首字符的大小,按ASC码比较的

传参时可以传数、传址但不能传数据类型
swap交换字符函数
一个变量的大小可能有好几个字节但只对应一个地址

p39
sizeof用于计算所占空间大小,多少个字节。如sizeof(数组名)   计算数组总大小。
!!!只要数组名不单独放在sizeof(arr)内和&arr,就表示首元素地址 。
数组名就是首元素大小 除了  sizeof(arr)和&arr 表示取整个数组这俩情况

strlen是求字符串长度的,它要找到/0之前有多少个字符

strlen()用法:int strlen ( const char * str );
1,先传地址进去2,以该地址为起点,往后数字符,直到/0为止 注意“abcd”d后有/0但{a,b,c,}后没有/0

*p+-整数  表示  跳过整数个元素的地址
把char类型字符传进去,其实传的是ASC码值
0x表示16进制    \0的ASC码值是0

p42 指针-指针 输出得到的是 元素个数

p47
对一维数组取地址,&arr,得到的是指向整个数组的地址,简称数组指针,那么对数组指针解引用,得到的就是arr这个数组名,即首元素地址
同理,二维数组的数组名,也是数组指针,指向二维数组首行,解引用得到首行指向首个元素的地址。 所以对二维数组名两次解引用相当于
a【0】【0】首个元素

返回型参数:这个参数既可以传入,又可以返回。 如 牛逼的指针,1.&px传入,2. 把 x赋给 *px,*px=x,返回px,指针就像左慈,既可以化生为
地址,加*,就化身为值,主动技:指值互换。

p49 
字符串的两种储存方式:1。放到数组里存储,即栈空间里,内容可修改2.放到指针char* p里 ,常量字符串放在常量区,不可修改

strlen是去找\0,找到\0停止。实现方法:1,计数器2,递归。不创建临时变量实现。?3.指针减指针
strlen的返回类型是 unsigned int ,所以strlen("ssf")-strlen("sdfggs")恒为正数。     size_t = =unsigned int

strcopy:利用指针解引用赋值  注:1.源字符串必须包含\0,因为strcopy是拷贝完\0才停止的 2.目标空间必须大于源空间,这样我考过来的
内容才放得下
strcap:追加字符串函数,1,找到目的地的\0停止2,把源从目的地的\0开始拷贝。 
但自己追加自己,就会把第一个自己的\0改了,这样就会导致 追加停不下来

strncap n表示count ,表示你要追加几个字符

strstr :在arr1里找子串

while()括号内本质填进去的是数字 非0数字表是真,可以进去,0为假,跳出循环
strlen的返回类型是 unsigned int 无符号
arr【5】表示里面只开辟了五个元素大小的空间 ; arr【】={1,2,3} 表示只开辟了三个该元素大小的空间
assert怎么用: 断言程序要想执行下去的先决条件 如assert(k>0) 如果k小于零了,就会报错。
如当你要对一个指针解引用前你要assert(p!=NULL),这样就不会因为你*NULL而报错。


p50 
strcmp比的是俩数组内相对的字符的ASK码值的大小 
有长度限制:strncpy输入几个,就拷贝几个 strncat最后主动加/0

strlen、strcpy、strcat、strcmp 本质:都和字符串里的\0有关
 长度不受限制的操作字符串的函数,都是找到\0,求长度、在\0前的内容拷贝、\0后面的追加、最坏情况比到\0前停止
缺陷:只能拷贝字符串,不能对整形、float操作
strncpy、strncat、strcmp  长度受限的字符串函数。

strncpy:当你指定拷贝个数多于我有的个数时,会自动补\0;
strncat: 追加完,主动放\0

p51 strstr查找字符串里的子符串是否存在,若存在,返回该处的地址,若不存在,返回NULL。
 在文档中: NULL,表示空指针       NULl/Null指 /0  把p=NULL,如果p原先有值,给它赋值NULL,相当于让它失忆。

p52

strtok  用途:切割由@ .和数字组成的字符串 每一次只return一部分的地址
第一次传arr,后面都传NULL就行,该函数会记住你分割到哪了。为什么一个函数传NULL能有资格往下操作?因为该函数有记忆功能,出了
函数变量不销毁,说明有static变量。
如123.111.122 第一次返回123的地址,记住第一个.  第二次传NULL,表示要改第二个. 然后返回111的地址,并记住第二个.

strerror(errno)把错误码  errno,传到strerror里,然后strerror 会return错误信息的指针
  printf(“%s”,strerror(errno))就能看到错误信息

字符分类函数:判断是什么类型的字符 islower isupper isdigit、isalpha isalnum。。。。。

字符转换函数:toupper转大写,tolower转小写
p54.
内存操作函数:不介意各种类型的数据。本质:一个字节一个字节操作

memcpy   
memmove负责内存重叠的拷贝  ,如把arr={1,2,3,4,5}里的 123 拷到234处 。
memcmp:去内存里比较  
memset   改内存;  而str改的是字符串,到\0前结束

p55:

结构体:
注:创建结构体变量的时候,后面有“;”,别漏。
结构体声明:struct(表明我是结构体)+stu(结构体名字),struct stu整体表示一个抽象数据类型。表示我声明了strcut stu这个结构体类型 
声明的目的是为了我创建该类型的变量。

结构体变量的定义(创建):一、全局变量,1.在}后面定义,然后,以封号“;”结尾 ,如 stuct S{}a,b;2.在main函数外部 如stuct S{};a;
二、 局部变量:在main函数内创建

即使你创建了两个 memberlist一样的结构体,编译器还是会认为这是俩不同的数据类型的结构体.

匿名结构体 
匿名结构体类型:只有struct 没有标签stu,所以只能在声明那创建变量,使用该变量,其他地方无法创建。

结构体的定义(变量创建):1.struct+类型名(结构体名字)+变量名2.在声明的{}后面+变量名 。
初始化:{}
结构体成员访问:是结构体变量时,用.访问成员  是结构体指针时,用->访问成员 。

p56:
结构体内存对齐:
结构体大小计算规则:1,第一个成员变量与该结构体变量在内存中的起始位置相同
2,其他成员变量在内存中放置的位置必须是在对齐数的整数倍处
对齐数是 该成员变量数据类型的大小和编译器默认值比较后的较小值,说白了对齐数就是个比较值。
但不是所有编译器都有默认的数
3.结构体内存总大小必须是 最大 比较值的整数倍

注:1.一格表示一个偏移量 
2,如果结构体里嵌套了结构体,内层结构体变量的对齐数是取其里面成员最大对齐数(如果直接取该结构体类型的大小太浪费)
3.不是所有的编译器都有默认对齐数,如果没有默认对齐数来比较,就拿该数据类型的大小,即该成员作为对齐数
4.让占用空间小的成员尽量集中在一起,能够有效节约空间

设置该规则原因:本质:拿空间换时间。1,平台原因,某些硬件平台只能访问某种类型类型的数据

修改默认对齐数:利用 #pragma修改 如 #pragma pack(4)设置默认对齐数为四  #pragam pack()取消默认对齐数,一般是2的次方数

offsetof计算偏移量的宏  参数:1,类型2,成员名

p57 

值传递:传本身的大小,但开辟了另一块空间存,把值拷贝到开辟的空间,修改此参数tmp,并不会对main函数的是s有任何影响。
因为传过去后,此函数tmp变量自己又重新开辟了一块属于自己的内存空间,所以你改tmp对s没有任何影响
缺点:函数传参,若传的是结构体本省,压栈的系统开销较大,导致性能下降

址传递:传本身的地址,我只开辟了另一个空间存地址。

所以你不修改变量时可以用值传递;你要修改变量时用址传递,即传该变量的地址。如 你传该变量的指针,被调函数用指针接受

注,误导:当你变量本身是指针,你传该指针还是值传递,但你传指针的指针,就是址传递了。
所以修改一个非指针变量传一级指针,修改一级指针,传二级指针


p57 
位段:是一种数据类型,长相类似于struct   。位段的位,表示二进制位
位段成员的数据类型几乎都相同、相统一。 第一个成员是int,接下来的成员基本上都是int,

位段空间的开辟:遇到char类型,一个一个字节开辟,若不够,再开辟;遇到int,四个四个字节开辟。

:+ 数字 表示多少位的字节。 如 int a=:2  表示a在内存中的大小占两个字节

目的:节省空间

注:位段, 不能跨平台,因为c语言标准没有规定位段,各个编译器按自己的理解去实现位段,
任何数据在计算机里都是放二进制,你打开编译器的内存,看到的是转换为16进制的数
优点:节省空间 缺点:有跨平台的问题

位段的应用:对要传输的数据封装。节约了空间


p59   
定义常量:1,#define  2,枚举

#define缺点:在预编译阶段,#define 是完成符号到数字替换的,不具有类型的概念

枚举定义:enum day 整个叫枚举类型名(整个作为类型)   {}内放的是:枚举可能取值(如常量),枚举类型的变量的可能取值只能从{}里找,
换句话就是,只能拿{}里的数 给枚举变量赋值. 创建变量的可能取值 只能是{ }里面枚举可能取值里的一个。

在定义时,可以给枚举常量的赋初始值。

枚举变量的大小 是 其中一个可能取值的大小,当是枚举常量时,变量大小就是4byte,因为 它只是其中一个的可能取值

联合(共用体):共用同一块空间
//联合体的大小:至少是最大一个成员的大小,因为至少得保证最大的成员得放得下
共用体使用条件:在使用a成员的时候,不能使用b成员,即两个成员不能同时使用某个空间。因为成员间的空间是有重叠的

用共用体实现判断大小端函数

//匿名联合体:缺点,创建完了匿名联合体类型后,只能在其}后紧接着创建变量,不能再其他地方继续创建变量,因为他没有名字

任何数据的大小都存在内存中,而要想访问内存,对地址解引用就行了
对于一个整形4byte   2进制中 即32bit(位)每一位两种可能0or1,共2to32.  等价于 十六进制0x中 每一位16种可能 16to8 
在内存中存的都是0x十六进制的数 类似于 0x00 00 00 00 (左边是高位,右边是地位)两个00够成一个字节,且表示一个16进制

大小端:指数据在内存中,存放的顺序(从低地址到高地址,还是从低到高)。内存编号小的是低地址,编号大的是高地址 
 数据的低位的放在低地址处,叫小端;地位低位放在高地址处,叫大端。

p60 
联合体计算1.联合体至少是最大成员大小       2,且联合体大小是最大对齐数 (数据类型和默认对齐数比较后的 较小值 ) 的整数倍
在vs里,默认对齐数是8。 遇到数组,是把数组元素的大小与默认对齐数比较

p63
内存分多块区域:栈区:放局部变量、形参 ,堆区(动态区):malloc、calloc,静态区:全局变量、static修饰的静态变量
代码段,放常量字符串

创建数组时,数组的元素个数得给定常量

malloc 、realloc、colloc,开辟完大小后,返回类型都是void*指针

动态内存分配:malloc :1,传参传的是大小(size)2,不会自己初始化内容。3.开辟失败,返回NULL,成功返回地
4,因此一定要检查malloc的返回值,万一NULL,不就用不了了吗 

malloc的标准格式 33分钟处,其实就是得判断是否开辟成功。

free(把借的动态内存还回去 )使用条件:只有开辟了动态内存空间,才需要用free来释放 。
几次malloc对应要几次free。必须成对出现,malloc~free、colloc~free,这是主动释放
如果在最后你不写free,当程序生命周期(即main函数结束)结束时,它也会还,这叫被动释放。

p64
calloc:1,传元素个数和单个元素size 2,自己会自动把开辟的空间 初始化为0  
calloc和malloc的区别只在于 传的参数和是否初始化

realloc(reallocate,重新开辟):动态调整动开辟的态内存的大小。
传的参数:1.传旧的、原先的、大小要被调整的 起始地址2,调整后的总大小。
注:如果里面数据你不人为初始化,就是随机值

realloc特点:
情况1,如果p后面的空间够,再原空间后面追加新的空间后,返回原来的地址p
 情况2,后面没有足够的空间让你追加。想追加的空间,但后面不够了,realloc会重新一次性开辟好新的空间,把旧空间数据p1烤到新空间p2,
并释放旧p的空间,返回新地址p2。    注:这种情况,返回的地址和原先的地址不同
情况3,开辟失败,返回NULL ,所以返回值得用新的变量来暂时保存,若不为空,则赋给旧指针
如果传NULL如 realloc(NULL,40)这里传NULL,表示我不需要修改地址,你只需开辟40大小的空间就行。等价于 malloc的功能

注:对malloc的返回值 必须要判断是否为NULL,若开辟失败,返回NULL,为NULL的空指针P,不能被解引用

使用free常见错误:
1.free只能释放起始为止的指针,指针位置变了,就不能释放空间了,所以用p+i的好处是在改地址的同时,就不会改变p的地址
2.重复free
3.free(p) 只是释放了p对应的内存空间,但该地址并没有变

内存泄漏就是一直占着内存不释放,导致内存越用越少,泄露叫leak

p66
局部变量在自己的{}内有自己的生命周期,在栈区的局部变量出了{}就会销毁
返回栈空间地址的问题,其实就是访问一块已经释放了的内存空间。当局部变量的生命周期结束后,会把借的空间返回操作系统。
所以可以用static修饰,使其变成在静态区的变量。因为栈区的空间释放特点和在静态区以及动态区的 特点不一样,栈区的变量,出了{}范围,
该变量对应的内存空间,就不属于它了

栈、堆,动态、静态区(数据段),全局变量,static修饰的变量、代码段(存不可修改的值),放只读数据,不能修改,如常量字符串

p67:

注:计算结构体成员的大小,即sizeof(struct S)不会计算柔性数组的大小,因为创建结构体时,它就没大小

法一:柔性数组,第27minute:结构体中包含了最后一个是数组的成员,该数组大小未给定,是未知的,且总大小可利用malloc开辟 ,
该数组叫柔性数组。之所以使柔软变化,使malloc结合realloc使用,就可以调整该数组大小

优点:1.只malloc一次2.空间连续3,提高访问速度
柔性数组使用条件:1,不能单独存在,得是在结构体中,前边必须至少还有一个成员,他不能是第一个。2,sizeof不计算它的大小
3,用malloc开辟大小,且要预留大小给柔性数组。如 malloc(sizeof(struct S)+10*(sizeof(int))

法二:利用指针,使用两次malloc。缺点:1.内存开辟不连续,有空档(内存碎片),cpu访问效率低2.要free两次,且要注意free顺序

局部性原理:你访问a处的数据,接下来你80%的可能性会访问a周边的数据,所以会把a周边的数据放到高速缓存区

在计算机里以二进制形式存放,但以十六进制or十进制给你展示

p68 柔性数组详解

71
.文件:  文件分为:1。程序文件(如后缀是 .h  .c .obj   .exe)2.数据文件(包含数据的)

文件名由 路径、文件名、后缀 组成

外储存器(又称:辅助存储器)是指除计算机内存及CPU缓存以外的储存器,此类储存器一般断电后仍然能保存数据。
常见的外存储器有硬盘、软盘、光盘、U盘等,外存储器的特点是容量大、价格低但是存取速度慢,所以一般用来存储暂时不用的程序和数据。

根据数据组织形式,数据文件, 分为两种类型:
1、 文本文件,把内存中的数据,借用ASC,以字符的形式存到外存(如在硬盘上文件)
2、二进制文件(把数据直接按二进制存到、输出到外存,不加转换) ,一般得用二进制的编译器才能看懂

文件缓冲区:  在内存里,为每一个进程开辟一个缓冲区,先把数据放到缓冲区,然后等其满了,再转移到其他地方。

任何外设和程序之间都会经过文件缓冲区。

缓冲区分:1,输出缓冲区(从内存到硬盘)2,输入缓冲区(从硬盘到内存)。如硬盘要数据,内存里的数据先给输出缓冲区,缓冲区满了,
再从输出缓冲区送到硬盘。

注:把内存当家,就可以理解出、入。

在程序数据区(在内存里)和硬盘之间会在内存开辟缓冲区。键盘(stdin)的输入会放到缓冲区,然后 scanf这种函数 从缓冲区拿数据
  1,当我要把内存里的数据输出到硬盘时,内存的数据会先把数据给到输出缓冲区
,当缓冲区满了后,再从缓冲区把数据给硬盘。2、当要把数据从硬盘输入程序数据区时,中间会有一个输入缓冲区

文件指针由来:
当我想操作一个文件时,不是对该文件直接操作,而是内存会在开辟一个叫文件信息区的空间,而是用文件指针,指向文件信息区操作。
(文件信息区会是一个记录你打开文件相关的信息的结构体用来存储你打开的那个文件的相关信息)该结构体类型被系统命名为FILE。
当你打开一个文件,系统会创建、填充合适的结构体;
当你改动文件数据时,该结构体里的内容也随之改变。打开一个文件,开辟一块文件信息区。指向文件信息区的指针叫文件类型指针.

对文件的操作整个流程:1.fopen 打开,并判断是否为 NULL 2. 利用函数操作文件 3. fclose 4.置空

errno 表示 error number
p72
fopen 1. 打开文件,有两种路径1,绝对路径,写全2,相对路径 用这些路径表示文件名。利用 ../ 巴拉巴拉 表示这是巴拉的上一级目录
    2.打开文件mode,即以哪种方式打开: 读,r,如果没你说的文件,就会打开失败。
 写,w(写的特点,如果你指定的文件不存在,创建新的文件,销毁旧的) a 追加            
fclose  打开了文件就得关闭
  
流stream= = FILE *     
默认一直打开的流设备1,键盘(类型是FILE* ,名字,stdin标准输入)屏幕(FILE* , stdout)
对文件进行读写的函数:  fputc  (fuction put character)fgetc 

操作流程:打开文件->创建与之对应的结构体FILE和FILE*->利用对文件进行读写的函数,修改函数->fclose

p73 注:这里的s是string 


project66 puts和gets的使用

puts(buf):puts是输出字符串函数。指把buf这个字符数组的内容打印到stdout  注:puts 自带\n   

gets(buf):是输入字符串函数,按回车gets结束输入,从stdin get字符串放到buf字符数组里。

操作文本行的读写函数:
fgets、fputs、行输入输出函数,使用一次往下get or put一行字符串。

fgets会把文本末你看不到的\0也获取到目标位置,但文本最后一行就没有\0了    fputs自带\0。参数:1.dest2.最多放几个3,source
特点:把get的内容放哪。

fputs 不会自带\0 参数1,放什么内容2,放到哪 dest 特点:输出到哪
从程序到文件是输出,数据从文件传到程序叫输入

格式化形式写文件:  格式,形如 %d 、%s 
printf是默认把数据放到stdout。
fprintf,把来自某地方的数据放到、输出到哪,但要反过来写。如fprintf(stdout,"%d %c",s.n,s,arr)。把结构体里的数据输出到屏幕。
把内容 输入 到你设定的流、文件, 是相对于标准输出流的格式化输出语句(printf), 不打印到屏幕,而打印到流对应空间
fprintf想不起来,就想想printf
格式化形式读文件:
scanf是把 %d 的内容放到&a,默认从键盘获取信息。如 scanf(”%d“,&a),scanf的流默认是stdin 

格式化输入输出函数: 形如"%d %s "这样的格数 输入输出的函数。
fscanf特点:从哪获取数据,放到哪去。fscanf(stdin,"%d",&a)
fscanf 从哪获取数据需要自己确定,要自己选一个文件指针 把该文件指针指向的内容放到对应的变量处,而不是从
如&a  从流对应的空间获取,放到&a  不从键盘get,从你选定的文件get数据

sscanf 第一个s是string ,指把来自字符串的数据放到哪
sprintf   指把数据 改成以字符串的形式 写到、输出到你指定的数组里。  
共同点:字符串类型与其他数据类型的转换,sprintf是把其他数据类型变成字符串;sscanf是把字符串放到其他数据类型的地方

P74

以二进制的形式读写 要用 rb、wb 模式来使用fwrite和fread

fwrite 把结构体数据写到文件、流里去
fread  把流、文件读到  结构体

P75
文件读写函数特点:1,按顺序依次往下读写,读一次指针+1

fseek:调整、改变文件指针的位置
ftell 告诉我相对起始位置的偏移量
rewind回到流的起始地址

注:空文件里第一个放的就是 EOF 为-1;所以你用fgetc读空文件,读到的就是-1
feof,function end of file,功能:判断怎么结束的。
判断当文件读取结束时,是什么原因导致文件读取结束。是因为读取失败才结束 还是因为读到文末的EOF才结束
feof返回非0,表示因为读到末尾的EOF才结束,表示因为读到EOF才结束;返回0表示因为读取失败而结束

perror 告你错误信息的函数,类似于strerror,使用更简单,无需加头文件
ferror,错了,返回非0值

p77
extern:用于申明我使用其他源文件的函数

编译原理解释了把C语言变成汇编语言的过程

编程环境1,编译环境,把c变成二进制代码。编译+链接2,执行环境。运行程序并产生结果。

编译:

1.预处理阶段,进行文本操作,.c->.i    
{
1。#define 把define 的符号 换成 对应的值
2.#include,包含头文件的内容,如把include的文件放入test.i
3.删除注释(因为机器不需要注释)
}

2.编译:.i->.s  把C代码变成汇编代码。通过做 {语法分析(语法对不对)、词法分析、语义分析(什么意思)、符号汇总 } 把c变成汇编代码
 符号汇总:把所有文件里的全局变量、函数名的符号汇总

3.汇编(变二进制):.s->.obj  在汇总好的符号基础上,各个文件各自形成符号表(符号+符号对应的 地址)。就有了多个。
把每个。c文件通过编译器变成对应的。obj文件

链接(其实就是大汇总):1. 合并段表(把所有。o文件上段的信息合并),最终
2.符号表的合并和重定位(把多个obj上符号表信息合并在一起,地址进行了重定位,即确定有效的地址 )
链接阶段,链接器链接所有。obj文件,然后最终生成.exe

编译环境,从源程序.c到.exe的过程,
.c(文本文件)->.i(还是C)->.s   (汇编代码)-> .obj (二进制代码)-> 链接(把。obj链接,再结合链接库)->。exe(二进制文件)

运行环境:
1.程序载入内存
2.调用main
3.为程序开辟栈空间    、静态区
4程序结束


预处理详解:

预定义符号:
文件名:__file__
行号:__line__
_DATE_可以用来记录你写的代码的信息(日志文件)
文件被编译时间__time__
__FUNCTION__所在函数名字

p79
预处理指令:#开头的
#define 1,定义标识符。定义某个符号的意义,可以是数,也可以是字符串等等各式各样的符号 #define MAX 100  注:是把符号内容整体替换
2,定义宏。  名子(参数)+和参数相关的表达式 ·    注:左括号必须紧贴符号名

#define的本质和功能 是实现符号、参数的替换!!!所以写宏的时候,对参数别吝啬()

注:宏不能递归 2.“”里定义的符号不进行替换  

#的作用:用在宏里,#x表示 显示传给x的变量的名字,而不转成对应的内容 
 如#define PRINT(X) printf(“helle”#X“word”,X)  ,你调用PRINTT(a),传了a,那么#X就可以显示 a这个变量名

## 一般用于宏定义,把##左右两端的符号合并成一个符号。 如  我是##猪   之后会变成 我是猪 ,可以理解为游戏里的合成。

... #pragma pack()  
p80
不好的习惯:带有副作用的宏参数 如 a++
c语言对应汇编代码讲解。

宏的特点:1.与参数类型无关,完成各种数据类型的替换,2执行速度快,小巧、轻便。宏是类型无关的。
缺点:1.不能调试,因为在预编译阶段就替换完成了,而调试时,已经时。exe了。 2.会因为替换进 表达式里的运算符的优先级 而带来运算的问题

函数特点:把参数计算好,再代入函数内部,是结果值传递,而不是对参数替换。
优点:1.可以调试
缺点:1.传参要考虑参数类型
2使用函数,存在调用函数和返回参数的额外开销。较宏来说,执行速度慢,笨重些。

好习惯:因为宏和函数比较相似,所以为了区分它们,一般把宏名大写,函数名小写

#undef NAME  删除已定义的叫NAME的宏定义

命令行定义

条件编译指令:根据条件,判断我要不要执行该代码 
如,#ifdef +define过的符号+表达式+#endif   #ifndef  #endif   #if defined( ) #endif  #if +常量+表达式 #endif
多分支是否参与编译: #if  #elif  #elif  #endif
 注:endif 是用于 结束前面的if

文件包含:所谓的头文件包含就是 把你 头文件 里的内容拷贝过来

包含本地头文件:用”“  查找策略:先在源文件的所在位置查找2。如果没找到像找库函数所在的路径的方式查找。
包含库里的文件:<>        查找策略:直接按头文件所在的安装路径去查找

<> 查一次""可能查两次 查找策略不同

嵌套文件包含:一个头文件,被多次引用。
解决头文件不被多次包含的方法:法一:法二:#pragma once

p82 offsetof 计算偏移量的宏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值