C语言私人学习笔记分享

函数和变量都需要满足:先声明后使用(重要)

在 函数的声明中,形参的名字可以省略

函数的定义是一种特殊的是声明,比声明更加强大;函数使用前必须进行声明,但不必要声明具体定义

.h------函数的声明

.c------函数的定义,说明使用

自己创建的头文件用""

标准库里面的头文件使用<>

隐藏关键代码------静态库

extern用来声明来自外部的符号

全局变量的作用域是整个工程

变量创建到销毁是他的一个生命周期

static只改变变量的生命周期(存储方式),不改变作用域

static修饰全局变量时,改变了全局变量的链接属性,使得外部链接属性变成了内部链接属性,这种变量只能在自己存在的.c文件中使用,其他文件不能使用

函数也具有外部链接属性,只要在其他文件中有正确声明,就可以直接使用

包含自己的头文件应该使用双引号

F5是让程序执行到(运行逻辑上的如循环)下一个断点处

调试就是指对单个语句进行研究和分析,开始执行不调试就是最好的例子Ctrl +F5

调试时候,箭头指向的是下一步即将执行的语句(按F10或者F11)

开始调试后才能看到监视窗口

四个二进制位可以表示一个16进制位

三个二进制位代表一个八进制位

每一个字节都有一个地址

栈区中内存的使用习惯:(不同的实现环境里存在区别,以下为X86环境)

优先使用高地址的空间再使用低地址的空间,数组随着下标的增长,使用的内存空间地址由低到高变化

不同的环境,bug或者release不同版本使用,都会影响最后程序执行的结果

release版本是存在优化的

数组在函数传参时不需要再继续定义类型

每次函数调用的时候在栈区里都会有属于他们的一个专属空间(运行式堆栈),使用结束后会销毁

循环是一种迭代,迭代不仅仅是一种循环

斐波那契数列------指数爆炸增长

青蛙跳台阶问题

反码,除符号位外其他位都取反,

补吗,反码+1

非负整数原码、反码、补码相同

char 是否为signed char取决于编译器,在VS上是signed

char类型的取值范围:-128~127

x86_------32位环境

x64 ------64位环境

数组的元素是连续存放的

指针变量的访问和加减的空间都取决于 他的类型

指针-指针必须类型一致,连续同一的空间内

数组名其实是数组首元素的地址

有两个例外:

1.sizeof(数组名)

这里的数组名表示的是整个数组,计算的是整个数组

2.&数组名

这里的数组名也表示整个数组,取出的是整个数组的地址

除此之外,所有的数组名都是数组首元素的地址

arr[i]也可以写成i[arr],但是可读性不高

数组传参,形参部分写成数组

数组传参的本质,是传递数组首元素的地址,所以形参即使写成数组的形式,本质上也是一个指针变量

数组传参部分可以写成数组也可以写成指针

指针类型决定了指针的差异

size_t是一种无符号整型

在 C 语言里,'\0' 代表的是 ASCII 码值为 0 的空字符(Null Character)。它的数值等同于整数 0。在字符串里,'\0' 一般被用作字符串的结束符,以此表明字符串到此结束。

一个局部变量未初始化的话,他的值是随机的

空指针不能直接访问

使用指针前检查是否为空指针,保证程序没有问题if(p !=NULL)

避免返回局部变量的地址

使用%s打印字符串的时候 ,只需要提供首字符地址就行

常量字符串不能被修改,数组能被修改

内容相同的常量字符串只需要保存一份

排序算法:

冒泡排序

插入排序

选择排序

快速排序

qsort底层使用的快速排序的思想

两个字符串比较大小不能使用><>=<=

应该使用strcmp,比较的是对应位置上字符的ASCII值大小,不是字符串的长度

比较两个结构体

声明函数时,可以省略参数变量名字

void* 泛型编程

sizeof是操作符不是函数

strlen是库函数,求字符串的长度,只能针对字符串(字符数组),遇到\0停

strlen的参数需要是地址 size_t strlen(const char* str)

sizeof内部的表达式是不会真实计算的,他是根据类型推断的

指针+1和类型有关

整数+1就是+1

元素要用{},用()会变成表达式

++变量后是会变化值的

赋值表达式的返回值是他赋值后的左操作数的值

strcat ()字符串追加

0------数字0

\0------\ddd,表示1-3个8进制数字,ASCII值就是0

'0'------数字字符0,ASCII值48

NULL------0,

强制类型转换是临时的

memcpy函数不负责重叠内存的copy,非要使用,结果未定义;只负责不重叠内存的拷贝

memmove来处理重叠内存的copy ,memove也可以处理不重叠,但是处理效率没有memcopy效率高

memset以字节为单位设置成想要的内容

memcmp返回值是整型,类比strncmp

大小端是超过一个字节的数据在内存中存储的时候,关于存储顺序的区分

char 有符号无符号取决于编译器,大部分编译器上char == signed char

整型提升时看存储的比特位的最高位,截断后的最高位才是符号位

浮点数在内存中有可能是不能精确保存的,2的负次幂的精度

offsetof 是一种宏 ,计算结构体成员相较于结构体变量起始位置的偏移量

给结构体开辟的空间是连续的,结构体的大小为最大对齐数的整数倍

如果嵌套了结构体成员,则对齐到他自己的成员最大对齐数的整数倍,他的内存就是他的元素和嵌套结构体内成员的所有最大对齐数的整数倍

结构体的内存对齐是拿空间来换时间·

修改默认对齐数为2的倍数

传值能做到的传地址一定能做到

变量名字由数字、字母、下划线组成但开头不能是数字

位段中的位指的是二进制的位,大小不能超过他自身的大小,指占多少个二进制位

位段是用来节省空间

char的底层逻辑也可以视为是整型家族(ASCII值)

给定空间后,在空间内部是从左向右使用还是从右向左使用,这个是不确定的;(取决于编译器)

当剩下的空间不足以存放下一个成员的时候,空间是浪费还是使用也是不确定的;(取决于编译器)

位段中最大位的数量是不确定的,根据不同的环境下变量的内存大小

位段可以节省空间但是有跨平台的问题存在

一个字节分配一个地址,一个字节内部里的bit位是没有地址的

因此不能直接用取地址对结构体里的变量进行赋值,需要中间变量,赋值

结构体、联合体、枚举

联合体的大小至少是最大成员的大小

内部元素不能同时使用

枚举可以提高代码的可读性

常见的两种申请内存空间的方式内存不能变化(创建变量创建数组)

开辟空间过大会返回空指针NULL,参数的单位是字节,申请空间成功的话返回起始地址malloc

0表示正常返回,1表示返回异常

mallco申请的空间叫动态内存,大小可以调整,他和数组开辟的空间位置不一样,局部变量和形式参数存在于栈区;全局变量和static修饰的静态变量放在静态区,动态内存放在堆区(malloc、free等)

size为0时,malloc的行为标准是未定义的,取决于编译器

free结束后,p指向的空间不属于当前程序但是还是找到这个空间(野指针),所以要p = NULL,

动态内存需要通过代码的方式释放内存,如果不释放的话,程序结束的时候也会被操作系统自动回收,不用的时候养成释放内存的习惯

free的参数指向的内存不是动态开辟的就不能动态释放,如果指向的是空指针,则函数什么都不做

对应malloc和free最好成对使用

realloc用来调整开辟的空间,参数必须是起始地址,空间由malloc、calloc或realloc开辟

realloc函数在调整空间的时候有两种情况,后面空间足够直接扩容返回旧地址,空间不够时,会在内存中再找一部分空间,直接满足新的内存大小的空间,(直接在内存的堆区找一块新的满足大小的空间),将旧的数据拷贝到新的空间,realloc会释放旧的空间,最后返回新的地址

realloc可能开辟失败,不建议用原指针接受开辟好的空间指针

不能对空指针进行解引用,不能对动态开辟的内存空间进行越界访问

非动态内存空间不能用free来释放,不能使用free来释放动态开辟内存的一部分(不再是起始地址)不能对同一内存空间进行多次内存释放(未将地址置为空指针)

动态开辟内存忘记释放(找不到,无法进行释放,导致内存泄漏)

动态内存管理是一把双刃剑,提高灵活的内存管理方式但同时也带来了 极大的风险

realloc不仅能调整空间,还可以申请空间,realloc(NULL,40)==malloc(40)

传空指针时也可以申请空间

对NULL空指针进行解引用,程序就会崩溃

常量字符串用双引号其实是首字符地址,字符串打印给个地址就行

没有初始化的指针不能进行解引用,是野指针

函数栈帧的创建和销毁在不同编译器上是略有差异的

寄存器eax ebx ecx edx ebp esp后面这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的(首尾地址),哪个函数用去维护哪个ebp叫栈低指针,esp栈顶指针

每一个函数调用都要在栈区上创建一个空间

栈帧其实就是内存空间

栈区空间的使用习惯是从高地址到低地址

在VS2013中main函数也是被其他函数调用的

一个word两个字节

给栈顶放一个元素:压栈push

从栈顶删除一个元素:出栈pop

柔性数组:

在结构体中,最后一个成员,未知大小

结构体中的柔性数组前面至少有一个其他成员。sizeof返回这种结构大小的时候不包含柔性数组的大小,包含柔性数组成员的结构体用malloc函数进行内存的动态分配,并且创建的内存大小要大于 结构体的大小,以适应柔性数组的预期大小

连续的内存可以提高访问速度,减少内存碎片产生,内部内存碎片(浪费的内存空间),外部内存碎片(不能被利用的内存空间),malloc用的越多内存碎片越多

代码段里的数据不能被修改(只读常量,常量字符串 )

栈区:局部变量,形式参数

堆区:动态申请的内存,

数据段(静态区):全局变量,静态变量(static)

栈区向下增长,堆区向上增长

数据放在文件中,文件放在硬盘上(持久性保存)

在设计程序时,谈两种文件:程序文件和数据文件

.obj为编译的文件(目标文件),.exe为可执行程序,都是程序文件

文件的内容不一定是程序,可能是程序运行的数据,存储数据或者写入数据

一个文件有一个唯一的文件标识

文件名:文件路径+文件名主干+文件后缀

根据数据的组织形式(内容),分为文本文件和二进制文件

打开流,读/写,关闭流

fgets只读n-1个字符,遇到\n会停止读取,只读到\n,一行一行读

sprintf其实是将格式化的数据写到字符串中,将格式化的数据转化成字符串

sscanf其实是从字符串中提取格式化的数据,可以理解为将字符串转换成格式化数据

offsetof----计算结构体成员相较于起始位置的偏移量

feof不是用来判断文件是否结束的

读取文件结束的原因:

读到末尾/遇到错误

feof判断文件结束的原因是不是遇到末尾

Sleep单位是ms

fflush可以刷新缓冲区,fclose也可以刷新缓冲区,fflush高版本可能不起效果

项目经过编译生成目标文件,目标文件经过链接生成可执行程序,在运行环境下输出结果

编译分为预编译(预处理)、编译、汇编

.obj文件称为目标文件,链接操作由链接器完成(link.exe)编译通过cl.exe

预处理阶段会进行预处理指令的处理,注释的删除,行号、文件名标识,为了方便生成调试信息,其实都是一些文本操作.i

编译就是把代码转换成汇编代码.s

汇编就是把汇编代码转换成机器可以执行的文件.o,是二进制指令(机器指令),生成了目标文件

链接阶段生成符号表,对不同符号表进行决议合并,进行符号表的合并和重定义

全局变量也能在符号表中体现

来自外部的函数和变量要在文件中进行声明

函数栈帧也叫做运行时堆栈

预处理阶段会对已经定义的符号进行直接的替换

\是续航符后面什么都不能加,应对于代码太长的情况

#define定义符号的时候最后不要加上分号

在写宏的时候一定不要吝啬括号,宏是带参数的

宏不支持递归,里面可以插入#define定义的其他符号

字符串内容不会搜索#define

宏比函数要节省操作时间,但宏仅适应于一些简单的操作,函数需要给类型

宏如果很长会增加我的代码长度,宏是无法调试的,宏由于类型无关,也不够严谨,宏可能会带来运算符优先级的问题,所以建议多带括号

函数的参数会计算好之后放进去

只有宏可以传类型

如果实现的逻辑比较简单不容易出错,考虑使用宏

#undef移除掉定义的宏