C语言(一)

一、C语言背景简介
1、GCC编译流程
预处理 :-E -->.i文件 预处理只是做头文件的展开,宏替换,条件编译选项的判断,注释删除等工作,不会报任何错误
编译:-S -->.s文件 检查代码是否有语法错误,无误后翻译成汇编语言
汇编:-c -->.o文件 将汇编文件翻译成二进制机器代码
链接:链接函数库,生成可执行文件
这里有个点就是在预处理阶段.H文件只是包含了大量的函数声明,而没有函数的实现,这些函数的实现都被封装进函数库里面了,所以你要使用这些函数的话,就必须链接这些函数库。
2、指针
(1)空指针并不是指针存储器为空或没有的概念,而是指针存放着特定的值–零值
(2)指针运算
px+n 表示的实际内存单元的地址量是(px)+sizeof(px指向的类型)n
px-py 两指针相减的结果不是地址量,而是一个整数值,表示两指针之间相隔数据的个数,计算公式如下:(px-py)/类型字节长度
不同数据类型的两个指针之间进行算术运算是无意义的
指针关系运算:指向地址大的指针大于指向地址小的指针
(3) 指针和数组
若指针变量p的值等于数组a的地址(即指针变量指向数组的第一个元素),则下面四个表达式是等价的,访问数组的第i+1个元素:a[i]<==>p[i]<==>
(a+i)<==>*(p+i)
注:指针变量和数组名在访问数组元素时,一定条件下具有相同的形式,但是本质上是不同的,指针变量是地址变量,数组的指针即数组名是地址常量
在C编程中,当一个字符指针指向一个字符串常量时,不能修改指针指向的对象的值
eg:char *p = “hello” *p = ‘h’ 错误
假设有一个二维数组a[2][2],则可以拆成一维数组的形式a[0],a[1],这是合法的,并不是主观瞎想出来的
const int *p 常量化指针目标是限制通过指针改变其对象的值
void p void指针变量是一种指向不确定数据类型的指针变量,可以通过强制类型转换,让该指针指向任何数据类型(该指针指向了实实在在的存放数据的地址)

(4) 野指针与空指针
野指针指向一个已删除的对象,或未申请的内存区域,野指针无法通过简单地判断是否为NULL避免,只能通过良好的编程习惯避免,对野指针进行操作容易造成程序错误。
野指针产生的原因大致有两种;一是定义指针的时候未初始化,这样指针会成为野指针,乱指一气;二是指针释放之后,没有置为NULL。例:charp;就是野指针。
空指针即"NULL",你可以简单地理解为值为0的指针(C语言中用(void
)0表示,C++中用0表示),就是为了规避野指针的,当野指针不知道指向哪里的时候就让它指向空指针。标准并没有对空指针指向内存中的什么地方做出规定,一般指向0地址,指向的地址不允许程序访问也不保存数据。
如何判断指针P是空指针?
if(p==0) if(p==’\0’) if(p==3-3) if(p==NULL) if(!p)
(5)对于32位系统来说,地址范围是0x00000000–0xffffffff,即空间为4G,每个字节都是一个地址,有一个编号,无论指针是什么类型,指针类型本身都是占4个字节。
(6)指针->整数->指针
int a = 123;
int *p = &a;
int b = (int)p;//把指针P的值当做一个整数取出来
char *p1 = (char *)b;把整数b的值作为一个地址赋给p1
3、函数相关
(1)函数传参
复制传参:把实参的值复制到形参的存储区域中,改变形参的值不会改变实参的值
地址传参:传的是实参的地址,相当于直接对实参进行操作
传递数组时,复制传参(int fun(int b[],int size))与地址传参(int fun(int *b,int size))没有区别,对b[i]操作都会改变b中元素的值,因为当形参是数组形式时,其本质上也是一个指针。
(2)函数指针
函数指针是用来存放函数地址(即函数名)的指针,当一个函数指针指向一个函数时,可以通过这个指针来调用该函数
格式: <返回值类型> <*fun> <参数列表>
函数指针数组时是包含若干个函数指针变量的数组
(3)递归函数
递归函数是指一个函数的函数体中直接调用或者间接调用了该函数自身的函数,一般递归函数的递归层次不会太高,在纸上画出来一步步的执行过程就不会出错。
(4)条件编译
编译器根据条件的真假决定是否编译相关的代码,常见的条件编译有两种方法:
一、根据宏是否定义
#ifdef <宏>
…如果宏已经定义,则编译该语句
#else
…如果未定义,则编译该语句
#endif
二、根据宏的值
#if <宏>
…如果宏值为真,则编译该语句
#else
…如果宏值为假,则编译该语句
#endif
(5)全局变量和局部变量
局部变量全局变量生存期函数调用开始至结束程序整个运行期间作用域定义变量的函数或符合语句内本文件赋初值每次函数调用时编译时赋初值,只赋一次未赋初值不确定自动赋初值0或空字符
main函数里面定义的变量依然是局部变量。
在同一源文件中允许全局变量和局部变量同名,但是在局部变量的作用域内,全局变量不再起作用。
当一个源程序由多个源文件组成时,非静态全局变量在各个源文件中都是有效的,静态全局变量则只在定义该全局变量的文件中起作用。

(6)return语句
函数里面的return语句,有的是起返回所需值的作用,有的是起到一个容错作用;如果main后面无return语句,则编译器会默认返回一个int型数值。
(7)形参与实参
形参在不被函数调用时不占内存,调用时会占内存,调用结束系统会自动释放掉,改变形参的值不会影响实参的值。
函数传指针是为了改变指针指向内容的值,函数传指针的指针是为了改变指针的值。
(8)strncmp(A,B,n)
字符串比较函数,从左到右依次比较字符串A与字符串B前n个字符的ASCII码,A>B返回1,A<B返回-1,A==B则返回0.
(9)strcpy(s1,s2)
将s2连同’\0’直接赋值到s1里面,从s1开头开始覆盖
eg:s1 = “helloworld” s2 = “hello” strcpy(s1,s2)
new s1 = “hello\0orld” printf("%s\n",s1)==>hello(遇到‘\0’截止)
(10)void
如果函数没有返回值,应声明为void类型;不加函数类型的函数默认返回值为整型,eg:add(int a,int b)
int main() 形参为空表示函数需要的参数个数不确定
int main(void) 表示无参数
eg;假如声明一个函数int fun(void);即无参,则调用时写成fun(),不能写成fun(void),因为"void"是类型

4、结构体
格式:struct 结构体类型名
{
成员表列;
};
struct 结构体类型名 结构体变量;
也可以在定义结构体的同时定义变量
结构体占用内存的实际大小可以同过sizeof(结构体类型名或者变量名)求出
注意事项:
(1)结构体类型变量不能作为一个整体加以引用,只能对结构体变量内部的各个成员分别引用
(2)如果结构体成员本身又是一个结构体类型,则要通过成员运算符,一级一级找到最低的成员进行赋值和运算
(3)在数组中,数组是不能彼此赋值的,但是同一类型的结构体变量可以相互赋值,但是不同类型的结构体变量之间不允许相互赋值,即使两者包含相同的成员

结构体数组:具有相同结构体类型的结构体变量可以组成一个结构体数组
eg: struct student stu[3];
结构体指针:
可以设定一个指针变量来指定一个结构体变量,此时该指针的值就是结构体变量的起始地址,格式如下:struct 结构体类型名 结构体指针;
访问结构体成员方法 :p->name, stu.name p是结构体指针,stu是结构体变量名
5、共用体
格式:union 共用体名
{
成员表列;
};
注意事项:
(1)共用体和结构体很相似,但是差别在于共用体的成员共用同一块内存区域,由于各成员的数据长度往往不同,所以共用体变量在存储时总是按照成员中数据长度最大的成员占用内存空间,而结构体则是按各成员数据长度之和占用内存空间。
(2)在共用体类型变量中起作用的成员是最后一次存放的成员
eg: a.i = 1;a.c = ‘a’; a.f = 1.5; 完成以上三个赋值运算之后,a.f是有效的,a.i和a.c无效了。
6、malloc和free
void malloc(size_t num)
void free(void p)
malloc函数不关心要申请的内存是什么类性,它只关心内存的总字节数,其次malloc和free是配对使用的,如果malloc返回的指针值丢失,则分配的堆空间无法回收,称为内存泄漏。malloc给有类型指针申请空间时要进行强转
7、强制类型转换
强制类型转换是一种不安全的转换,会造成数据溢出和精度丢失,一般都是将高级类型转换成低级类型
char/short–>int–>long–>double<–folat
8、算术运算符优先级
++、–优先级比算术运算符高
关系运算符有值,其值为布尔值
‘,‘逗号运算符为最后一个表达式的结构,但是前面的表达式要参与last运算
sizeof是运算符不是函数
9、数组相关
(1)数组不初始化,元素为随机值
(2) 不能一次性引用数组中的所有元素 eg:int a[5];printf("%d\n",a)错
(3)数组不能相互赋值 str1=str2错误
不能用变量定义数组维数 int data[i]错误
char s2[10] s2[10] = {‘1’,‘2’,‘3’} 错误,不能分开初始化
C编译器不会检查下标的越界(int a [10],打印a[10]不会报错,有值不确定)
数组下标在内存中表示偏移量
(4) 数组名a表示数组首元素的地址,是一个地址常量而不是变量,a+1是对的,但是a++是错的。
&a表示数组的首地址
eg:int a[2];printf("%p\n",a);printf("%p\n",&a)
上面两条打印语句结果是一样的,但是本质不一样,打印a+1和&a+1的结果就不一样了,后者加的类型是整个数组
(5)打印二维数组的第一行
char s[2][5] = {“hello”,“123”};
printf("%s\n",&s[0][0]);
printf("%s\n",s[0]);
上面两条打印语句作用相同
10、字符串相关
(1)只有字符串是以空字符’\0’结尾,空字符占用内存,但是不计入字符串长度,‘\0’对应的整型值是0
(2)char s1[5] = {‘h’,‘e’,‘l’}; //后面自动补空字符
int a[5] = {1,2,3}; //后面自动补整数0
char s[5] = {“hello”};字符串溢出不报错,此时只分配5个字节内存,没有’\0’的位置,所以如果打印该字符串,有可能会打印5个字节以后内存的内容。
(3)字符串在内存中存储时,会多用一个字节存储’\0’,表示字符串存储结束,’\0’也可以与一般字符数组做区别
(4)scanf("%s",str) //后面直接用数组名,不用取地址
11、scanf函数
(1)scanf("%d",&a)为什么不加’\n’
此处末尾’\n’可以理解为过滤空格,制表符,回车等输入,如果加了’\n’,那么输入完数据之后需要再输入一个非(空格,制表符,回车)才表示数据输入完毕。
(2)scanf("%4d%2d%2d",&a,&b,&c)
若输入19910107,则a=1991,b=01,c=07
12、switch语句注意点
(1)switch(表达式) 表达式类型为int、char、枚举
(2) 表达式与标签是等式而不是关系式
(3)执行完对应case语句之后,如果该case语句没有break,那么程序不会结束,而是会执行下一条case语句(不管标签是否相等),直到遇见break为止
13、大端序与小端序
大端序:数据的高位字节存放在地址的低端,低位字节存放在地址的高端
小端序:数据的高位字节存放在地址的高端,低位字节存放在地址的低端
注:大小端序和硬件体系结构有关和操作系统无关
x86架构的PC都是小端序,arm架构大小端序可选,其他架构都是大端序
eg:int a = 0x12345678 左边的12是高位字节,右端的78是低位字节,按小端序存储如下图所示
低地址78(0111 1000)56(0101 0110)34高地址12
14、虚拟内存划分(32位,共4G)
内核栈区(4~8M)堆区(300M左右)DATA段代码段
上图自下往上地址是0x00000000–0xffffffff
用户空间(共3G大小):
代码段:存放程序代码
DATA段(分两个区域):bss区存放未初始化全局变量,静态变量;数据区存放已初始化全局变量,静态变量。(其实有三个区域,还划分一个文字常量区,存放常量字符串)
堆区:动态内存分配,由用户申请和释放,
栈区(堆栈):由编译器自动分配释放,存放函数的参数值,返回值,局部变量
注意:堆区生长方向由低地址向高地址生长,栈区由高地址向低地址生长,怎么理解呢?
低地址栈高地址
假设上面是栈区的一块内存,那么按照栈的存储方式,第一个数据应该存放在高地址的位置,第二个数据存放在低一个字节的位置,这样数据的存储方式就是从高地址向低地址生长,而堆区与其相反
…杂乱的宝贝…
1、float b = 111.23456
printf("%.7f",b);
指定小数点后7位,精度不够,后面输出的2位不是自动补零而是随机值
float 与0不能用==比较
if((-0.000001<float) && (float<0.000001))
2、出现段错误可能的原因
野指针、数组越界、使用非法内存
3、缓存的存在是为了解决内存和CPU存取速度的差异
4、sleep()
程序里有sleep延时函数,则CPU先不给该进程或线程分配时间片。
sleep()函数在执行时,CPU处于空耗状态
alarm()函数时在很短的时间内完成操作,然后CPU继续往下执行,到时间后,再发送一个信号。
5、半双工指同一时间内,单向收发,全双工指同一时间内双向收发
6、IP地址转为整数
eg:172.22.1.X<=>172
224+22*216+1
2^8+X<=>172<<24+22<<16+1<<8+X
7、网络和行业相关最新动向
lora,IPV6,zigbee,Bluetooth(5.0),802.11.ah(低功耗)
计算机视觉、slam激光雷达(室内规划)、智能仓储、智能家居、无人机自动驾驶机器人、智能穿戴设备
8、原码、反码转换
int main
{
char a[1000];int i;
for(i = 0;i<1000;i++)
{
a[i] = -1-i;
}
printf("%d\n",strlen(a));
return 0;
}
答案:255
相应知识点如下:
(1)负数原码的表示方法是先用所有的位(8位)表示它的绝对值,然后在最前面加一个1作为符号位,不一定非要用8位二进制位表示,以前理解误区。求负数的补码是符号位表示不变,其他位取反然后加1.
(2)char型有符号数表示范围-128~127
(3)在ASCII表中
‘\0’ 对应十六进制数 0x00 ----空字符
’ ’ 对应十六进制数 0x20 ----空格字符
‘0’ 对应十六进制数 0x30 ----零字符
(4)过程(换算补码超出8位则溢出)
i = 0 a[0] = -1 补码 1 111 1111 计算机存储补码0xff
i = 127 a[127] = -128 补码 1 1000 0000 计算机存储补码0x80
i = 128 a[128] = -129 补码 1 0111 1111 计算机存储补码0x7f

i = 255 a[255] = -256 补码 11 0000 0000 计算机存储补码0x00
i = 256 a[256] = -257 补码 10 1111 1111 计算机存储补码0xff

分析:由上面的过程可知i值0到255是一个循环节,后面依次循环;此外当i=255时,在计算机中补码表示恰好为‘\0’的ASCII码表示,所以打印到此处结束,strlen(a)的结果为255
9 有符号数的表示范围(-128到127)
正数有符号数 0 000 0000 - 0 111 1111 即0到127
负数有符号数 1 000 0000 - 1 111 1111 负数补码求法是符号位保持不变其他位取反然后加1,即 1 1 000 0000到1 000 0001,即-128到-1。
(-128得到的时候有效值是8位表示的)
10 二进制小数和十进制小数的转换
二进制转十进制小数 :整数部分乘2的正次方,小数部分乘2的负次方
十进制转二进制小数 :整数部分和小数部分分开转换,小数部分乘以2取整,(减去整数之后的小数部分)继续取整直到没有小数部分为止,正向排序。
11 float存储方式
float类型数据在内存里面是以指数形式存储的(所以同样四个字节,float类型数据范围要大得多)
31*****************************************************0
1位(符号位表示正负) 8位(指数位) 23位(尾数部分即底数)
eg:8.25
转换为2进制小数1000.01,用科学计数法表示为1.00001
2^3,存储格式
0 10000010(3+127) 00001000000000000000000

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

foreverwlh

你的鼓励将是我创作的巨大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值