C语言

2022-04-17  本文已影响0人  漱衣仁止

函数

传值调用

传址调用  区别?

函数内部想求参数的个数是不可能的

函数的嵌套调用

函数的链式访问:把一个函数的返回值作为另一个函数的参数

函数的递归:自己调用自己。递归会很迷,需要注意的是最后一层执行完之后还会返回上一层执行完整的剩下的代码。递归必须有条件,每次递归会越来越接近这个条件。递归返回时从下往上返回。

库函数和操作符

strlen和sizeof没有什么关联, 

sizeof计算变量、数组、类型所占空间大小-单位是字节,包括“\0”,是操作符;strlen计算字符串的长度,“\0”之前的字符个数,是库函数,使用要引头文件。 

数组

指定数组大小一定要是常量。

不完全初始化,剩下的默认为0。字符默认"\0"结尾。

数组在内存中是连续存放的,int类型是四个字节存储的。

二维数组行可以省略,列不可以省略。二维数组在内存中也是连续的,第二行的存储紧跟在第一行后面。

arr是数组,对数组进行传参,实际上传递过去的是数组首元素的地址。数组名就是首元素地址,但是有两个例外,即sizeof(数组名)-数组名是整个数组,计算的是整个数组的大小,单位是字节;&数组名,取出的是整个数组的地址。

操作符

算术操作符:除了%(取模)操作符之外,其他的几个操作符可以作用于整数和浮点数,%只能作用于整数。

移位操作符:>>移位分为算数移位和逻辑移位,算数右移规则为右边丢弃,左边补原符号位(0为负,1为正);逻辑右移规则为右边丢弃,左边补0.,通常见到的是算数右移。对于移位符,不要移动负数位,这个标准是未定义的。

整数的二进制表示有原码、反码、补码,存储到内存的是补码。反码是原码第一位符号位不变,其余位取反;补码是反码+1.正数的原码、补码、反码相同。打印出来的是原码,(补码-1)取反。操作的是补码,输出的是原码。

位操作符

赋值操作符

复合赋值符:+=

单目操作符:前置++,规则为先++,后使用;后置++,规则为先使用,再++。

一个字母是一个字节,一个整数是四个字节,短整型是两位,一个字节是8位。32位电脑一个存储空间就是4个字节,不论是什么类型的都是4个字节。

关系操作符:>,>=,<,<=,!=,==

逻辑操作符:&&,||。 注意逻辑操作符的短路原则,计算时只要出现假就不会继续往下算了,例如与操作符,只要一个为假,算到这个假之后的便不会计算了。或操作符只要有一个为真便为真,所以算到这个真便不用往下计算了。

条件表达式:也叫三目操作符,exp1 ? exp2 : exp3,如果表达式1的结果为真,则表达式2的结果为整个表达式的结果,如果表达式1为假,则表达式3的结果为整个表达式的结果。

逗号表达式:exp1, exp2, exp3, ... , expN从左到右依次计算,最后一个表达式的结果是整个表达式的结果。

下标引用、函数调用和结构成员:函数调用操作符接受一个或多个操作数,第一个操作数是函数名,其余的操作数就是传递给函数的参数。结构体struct用来创建类型,利用这个类型可以来创建变量。

->是结构体指针操作符,结构体指针->成员名。

隐式类型转换有整形提升和算术转换。

整形提升会自动补全。负数整形提升高位补1,正数整形提升高位补0,无符号整形提升高位补0.两个char相加会发生这种情况。非int类型却进行int类型计算的都会发生整形提升。

算术转换:如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作书的类型,否则操作就无法进行。

总结,我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那么这个表达式就是有问题的。

指针

指针是什么:指针是一个变量,这个指针变量p是用来存储地址的,可以说指针就是地址,p的本质是变量。

一个内存单元大小为一个字节,即8个二进制位。

由此32位的机器上,地址是32个0或1组成二进制序列,那地址就是4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节;在64位机器上,如果有64个地址线,那一个指针变量的大小时8个字节才能存放一个地址。

总结:指针是用来存放地址的,地址是唯一标示一块地址空间的。

指针的大小在32位平台是4个字节,在64位平台是8个字节。

探讨既然指针都是四个字节或者八个字节,那区分指针类型还有什么意义呢?

2个16进制位 = 8个2进制位 = 1个字节

为什么一位十六进制数对应四位二进制数?

十六进制数的进率是16,二进制数的进率是2,且16=2^4,说明二进制数连续进位4次,等效于16进制数进1位。这么说可能不好理解,那么举个例子吧,比如15+1=16,用二进制表示就是1111+1=10000,用十六进制表示就是F+1=10。这也就说明了一位十六进制数对应四位二进制数了(F对应1111)。一个16进制位16个状态,4个二进制位也是16个状态。0x开始的数据表示16进制,0x不占空间。

指针和指针类型:指针类型决定了解引用操作的时候,能够访问空间的大小, int *p, *p 能够访问4个字节;char *p, *p能够访问1个字节;double* p, *p能够访问8个字节。

指针类型决定了指针走一步走多远(指针的步长)。int* p+1 ---> 4; char* p+1 ---> 1; double* p+1 ---> 8.

总结:指针类型决定了向前或者向后走一步有多大(距离)。指针类型决定了。对指针解引用的时候有多大的权限(能操作几个字节),比如:char*的指针解引用就只能访问一个字节,而int*的指针解引用就能访问四个字节。

野指针:就是指针指向的位置是不可知的(随机的 不正确的 没有明确限制的)。

局部变量不初始化,里面默认放的是随机值。

局部的指针变量,就被初始化随机值,会导致野指针问题。

指针越界访问也会导致野指针问题,越界指针指的是当指针指向的范围超出数组arr的范围是,p就是野指针。

指针指向的空间释放,也会导致野指针问题。这个现象会出现在引用函数时候,因为函数中创建的局部变量在函数范围内创建,一旦出函数就会被销毁,所以用指针改变函数局部变量的地址时,访问的就是被释放的空间。 只要是访问临时变量的都是有问题的。

如何避免野指针:指针初始化,小心越界指针;指针指向空间如果释放则可以置为NULL;指针使用之前检查有效性。

NULL是用来初始化指针的。

指针运算:指针+-整数;指针-指针;指针的关系运算

指针+-整数作用是往后/前访问,赋值。

指针-指针作用是地址-地址,得到的是中间的元素个数(大-小),注意这两个指针 一定是指向同一块空间的。

指针的关系运算,就是比较大小。标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

指针和数组:数组名是首元素的地址,有两个例外,即

1. &arr- &数组名- 数组名不是首元素地址-数组名表示整个数组  -&数组名 取出的是整个数组的地址。注意打印结果还是首元素的地址,在arr+1/&arr+1的时候就可以看出区别了,一个加了一个元素的大小地址,一个是加了整个数组大小地址。

十六进制转十进制

2.sizeof(arr) - sizeof(数组名) - 数组名表示的是整个数组 -sizeof(数组名) 计算的是整个数组的大小,单位是字节。

二级指针:int** ppa就是二级指针,取的一级指针变量的地址。*代表ppa是指针,int*表示ppa这个指针指向的类型是int*。**ppa先通过*ppa找到pa,然后对pa进行解引用操作:*pa,那找到的是a.

指针数组:本质是数组。是存放指针的数组。

数组指针:本质是指针。

结构体

个人感觉像python的类。 

一. 结构体类型的声明

结构是一些值得集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

结构体声明:

struct tag

{

    member-list;

}variable-list;

可以对类型进行重命名。

结构体的成员可以是标量、数组、指针、甚至是其他结构体。

二. 结构体初始化

三. 结构体成员访问

结构体.变量 or 结构体->变量

四. 结构体传参

形参是实参的拷贝,所以可以传地址过去(4/8个字节)。

函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。

解释什么是栈区?

内存空间分为三大部分:栈区(局部变量,函数的形式参数,函数调用也开辟空间)、堆区(动态内存分配,malloc/free,realloc,ralloc)、静态区(全局变量、静态变量)。

栈是先进后出,后进先出。放入一个元素叫压栈,删除一个元素叫出栈。

栈区的默认使用:先使用高地址处的空间,再使用低地址处的空间。

可以自己查函数栈帧的创建你和销毁来了解汇编角度的压栈。

结论:结构体传参要传结构体的地址

实用的调试技巧

一. 调试的基本步骤

发现程序错误的存在.

以隔离、消除等方式对错误进行定位.

确定错误产生的原因.

提出纠正错误的解决办法.

对程序错误予以改正,重新测试.

二. Debug和Release的介绍

Debug通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。

Release成为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便于用户很好地使用。

三. 快捷键

F5-启动调试经常用来直接调到下一个断点处, -和F9配合使用,F9是设置断点,表示代码执行到某个地方停下了。

断点,调试器的功能之一,可以让程序中断在需要的地方,从而方便其分析。断点也可以在一次调试中设置断点,下一次只需让程序自动运行到设置断点位置,便可在上次设置断点的位置中断下来,极大的方便了操作,同时节省了时间。

F9-创建断点和取消断点

F10-逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。

F11-逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最常用的)。

CTRL+F5-开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。

三. 调试的时候查看程序当前信息

四. 如何写出好(易于调试)的代码

优秀的代码:

1. 代码运行正常

2. bug很少

3. 效率高

4. 可读性高

5. 可维护性高

6. 注释清晰

7. 文档齐全

常见的coding技巧

1. 使用assert。断言,若为真什么都不会发生,为假会报错

2. 尽量使用const

3. 养成良好的编码风格

4. 添加必要的注释

5. 避免编码的陷阱

小tips:

const放在指针变量的*左边时,修饰的是*p,也就是说:不能通过p来改变*p(num)的值;

const放在指针变量的*右边时,修饰的是指针变量p本身,p不能被改变了。

五. 编程常见错误

编译型错误

直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单。

链接型错误

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误。

运行时错误

借助调试,逐步定位问题。最难搞。

温馨提示:做一个有心人,积累排错经验。

C语言进阶

数据的存储 

一. 数据类型详细介绍

C语言类型分为

1. 内置类型:char,short,int,long,float.double

2. 自定义类型(构造类型)

类型的意义

1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)

2. 如何看待内存空间的视角。

整形家族:char,short,int,long

浮点型家族:float,double

构造类型:数组类型,结构体类型struct,枚举类型enum,联合类型union

指针类型:int *pi,char *pc,float *pf,void *pv

空类型:void表示空类型(无类型)。通常应用与函数的返回类型、函数的参数、指针类型

二. 整形在内存中的存储:原码、反码、补码

原码 反码 补码

计算机中的有符号数(整形)有三种表示方法,即原码、反码和补码。(无符号数的原码反码补码相同)

三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相同。

原码:直接将二进制按照正负数的形式翻译成二进制就可以。

反码:将原码的符号位不变,其他位依次按位取反就可以得到。

补码:反码+1就可以得到补码。

总结:正数的原、反、补码相同。负数的计算规则如上。

对于整形来说:数据存放内存中其实存放的是补码。

有符号的char的范围是:-128 -> 127;

无符号的char的范围是:0->255.

注意在使用无符号数的时候,条件使用不当很容易陷入死循环。

为什么呢?

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码与原码相互转换,其运算过程是相同的,不需要用额外的硬件通路。

三. 大小端字节序介绍及判断

大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;

小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。

四. 浮点型在内存中的存储解析(??)

常见的浮点数

3.14159 1E10 浮点数家族包括:float、double、long double类型。浮点数范围:float.h中定义。

IEEE 754规定??

指针详解-指针的进阶

指针概念:

1. 指针就是个变量,用来存放地址,地址唯一标识一块空间内存

2. 指针的大小是固定4/8个字节(32/64位平台)

3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限

4. 指针的运算

一. 字符指针

指向字符的指针char *;实际存储的是字符串首字母的地址存放到指针变量里。

二. 数组指针

是指针,能够指向数组的指针。int (*p)[5] = &arr

三. 指针数组

其实是数组,用来存放指针的。

四. 数组传参和指针传参

五. 函数指针

六. 函数指针数组

七. 指向函数指针数组的指针

八. 回调指针

九. 指针和数组面试题的解析

字符串函数&内存函数使用和剖析

strcat()自己追加自己会崩溃。

自定义数据类型详解(结构体+枚举+联合)

内置类型-C语言自己的数据类型:char, short, int, long, float, double

复杂类型-自定义类型:结构体、枚举、联合体

位段:位段的声明和结构是类似的,有两个不同。

1. 位段的成员必须是int, unsigned int, signed int

2. 位段的成员后边有一个冒号和一个数字。

枚举:就是列举

联合体(共用体):联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列成员,特征是这些成员公用同一块空间(所以联合体也叫共用体)。

动态内存管理

一. 为什么存在动态内存分配

因为当前开辟空间的方式不满足需要多少创建多少的想法。动态内存分配实在堆上。

二. 动态内存分配函数介绍

三. 常见动态内存错误

四. 几个经典的笔试题

五. 柔性数组

文件操作

一. 什么是文件

磁盘上的文件是文件。

但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件

程序文件

包括源程序文件(后缀.c),目标文件(windows环境后缀.obj),可执行程序(windows环境后缀为.exe)

数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取的数据文件,或者输出内容的文件

本章讨论的是数据文件。

在以前各章所处理数据输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上八数据读取到内存中使用,这里处理的就是磁盘上的文件。

二. 文件名

一个文件药有唯一的文件标识,以便用户识别和引用。

文件名包含3部分:文件路径+文件名主干+文件后缀。

例如:c:\code\test.txt

为了方便起见,文件标识被称为文件名

三. 文件类型

根据数据的组织形式,数据文件被称为文本文件或者二进制文件

数据在内存中以二进制的形式存贮,如果不加转换的输出到外存,就是二进制文件

如果要求在外村上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件

一个数据在内存中是怎么存储的呢?

字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以用二进制形式存储。

如果有整数10000,如果以ACSII码形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘中只占4个字节。

四. 文件缓冲区

五. 文件指针

六. 文件的打开和关闭

七. 文件的顺序读写

八. 文件的随机读写

九. 文结束的判定

程序的环境和预处理

一. 程序的翻译环境

二. 程序的执行环境

在ASNI C的任何一种实现中,存在两个不同的环境。

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境,它用于实际执行代码。

三. 详解:程序的编译(预处理操作)+链接

四. 预定义符号介绍

五. 预处理指令 #define

六. 宏和函数的对比

七. 预处理操作符#和##的介绍

八. 命令定义

九. 预处理指令 # include

十. 预处理指令 #undef

十一. 条件编译

推书

《剑指offer》65道题

《C陷阱和缺陷》

上一篇 下一篇

猜你喜欢

热点阅读