c语言复习

c语言基本概念

源码 ,反码 ,补码

正数的源码反码补码都是一样的。
负数的补码是,符号位不变,其余位取反加1。
计算机存储负数,存储的是补码,这样方便计算机的计算。
需要注意的是,在进行移位运算时,有符号和无符号是不同的
有符号,右移,对于正数补0,负数补1
无符号时都是正数,所以无论左移还是右移都是补0的。

 15     unsigned int a = 0x80000000;
 16     printf("a:%x, a >> 31:%x, a >> 31:%d\n", a, a >> 31, a >> 31);
 17 
 18     int b = 0x80000000;
 19     printf("b:%x, b >> 31:%x, b >> 31:%d\n", b, b >> 31, b >> 31);
 20 
 21     int c = 0x40000000;
 22     printf("c:%x, c << 1:%x, c << 1:%d\n", c, c << 1, c << 1);
 23 
 24     int d = 0xF0000000;
 25     printf("d:%x, d << 1:%x, d << 1:%d\n", d, d << 1, d << 1);  

结果:

a:80000000, a >> 31:1, a >> 31:1
b:80000000, b >> 31:ffffffff, b >> 31:-1
c:40000000, c << 1:80000000, c << 1:-2147483648
d:f0000000, d << 1:e0000000, d << 1:-536870912

应用一下:
写一个小函数,打印出传入数据的二进制。

  8 void main(int argc, char* argv[])
  9 { 
 10     if (argc < 2) {
 11         printf("the num of param is wrong:%d\n", argc);
 12     }                                                               
 13                                                                     
 14     int param = atoi(argv[1]);                                      
 15     printf("source code %d :\n", param);                            
 16                                                                     
 17     int bit = 0;                                                    
 18     for (int cnt = 31; cnt > -1; cnt--) {                           
 19         bit = (unsigned int)(param & (0x1 << cnt)) >> cnt;          
 20         printf("%d", bit);                                          
 21         if (0 == (cnt) % 4) {                                       
 22             printf(" ");                                            
 23         }                                                           
 24     }                                                               
 25     printf("\n");                                                   
 26 } 

输出结果:

source code -10 :
1111 1111 1111 1111 1111 1111 1111 0110 

如果将第19行换成

bit = (param & (0x1 << cnt)) >> cnt;

则输出的结果将会是:

source code -10 :
-1111 1111 1111 1111 1111 1111 1111 0110 

这种情况就是有符号的数被移位到最高位,变成负数,右移的时候会被补1,导致输出的第一个数是-1.因此有符号的数,进行移位操作的时候还是需要小心的。

关于单字节加减越界的思考:

uint8_t num1 = 255;
uint8_t num2 = 0;
int8_t num3 = 127;
int8_t num4 = -128;

printf(" num1:%d ", ++num1);
printf(" num2:%d ", --num2);
printf(" num3:%d ", ++num3);
printf(" num4:%d ", --num4);

因为计算机是使用源码进行计算的,所有将数据换成源码就知道啦。
255 源码 1111 1111 和 1 源码相加 0000 0001 为 0
0 源码 0000 0000 和 -1 源码相加 1111 1111 为255
127 源码 0111 1111 和 1 源码相加 0000 0001 为 -128
-128 源码 1000 0000 和-1的源码相加 1111 1111 为127
可以总结出来计算的时候,比较像是一个循环。

sizeof和strlen的区别

sizeof 是一个关键字,并不是一个函数,计算的是变量的大小
strlen计算的是字符串地址,一直到\0为止的,字符串的大小
sizeof 在编译的时候就已经确定了计算的结果,而strlen是在程序跑起来之后,才会得出计算结果。
以上是原理,sizeof之所以是关键字,是因为它不需要包含其他头文件,并且后面可以不用打括号
64位系统下判断,以下代码输出结果:

  4 void fun1(char *buffer)                                                                             
  5 {                                                                                                   
  6     printf("fun sizeof a:%d\n", sizeof(buffer));                                                    
  7     printf("fun strlen a:%d\n", strlen(buffer));                                                    
  8 }                                                                                                   
  9                                                                                                     
 10 int main()                                                                                          
 11 {                                                                                                   
 12     char a[15] = "hello word";                                                                      
 13     printf("sizeof a:%d\n", sizeof(a));                                                             
 14     printf("strlen a:%d\n", strlen(a));                                                                                                                                                   
 15     printf("strlen &a:%d\n", strlen(&a));                                                           
 16     fun1(a);                                                                                        
 17     return 0;                                                                                       
 18 } 

因为c语言里面,数组传递的是数组的地址,根据之前讲的原理,sizeof的结果会变的,而strlen的结果是不变的

sizeof a:15
strlen a:10
strlen &a:10
fun sizeof a:8
fun strlen a:10

指针的算数运算

原理:指针的算数运算,只有加减,没有乘除,它的意义是,在当前地址的基础上,加上或者减去,指针指向的数据类型的基础上,加上或者减去n。所以具体输出的结果需要看,指针指向的数据类型。

 12     int a[5]  = {0};                                                                                
 13     char b[5] = {0};                                                                                
 14     printf("&a[3] - &a[0] :%d\n", &a[3] - &a[0]);                                                   
 15     printf("&b[3] - &b[0] :%d\n", &b[3] - &b[0]);                                                   
 16     printf("((int)&a[3] - (int)&a[0]) / sizeof(char) :%d\n", ((int)&a[3] - (int)&a[0]) / sizeof(char));
 17     printf("((int)&b[3] - (int)&b[0]) / sizeof(char) :%d\n", ((int)&b[3] - (int)&b[0]) / sizeof(char));

根据以上原理打印结果为:

&a[3] - &a[0] :3
&b[3] - &b[0] :3
((int)&a[3] - (int)&a[0]) / sizeof(char) :12
((int)&b[3] - (int)&b[0]) / sizeof(char) :3

数组名和数组地址

数组名:代表的是数组的首元素的地址,类型为数组元素的类型。
数组地址:代表的是数组的地址,类型为整个数组。

int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));

结果为2,5

判断大小端

小端:高字节放在高位,低字节放在地位
大端:高字节放在地位,高字节放在高位

 12     int a = 0x12345678;                                                                             
 13     char b = *(char*)(&a);                                                                          
 14     if (0x78 == b) {                                                                                
 15         printf("little\n");                                                                         
 16     } else {                                                                                        
 17         printf("big\n");                                                                            
 18     }                                                                                               
 19     return 0;  

assert的使用

这是一个函数,只有在debug模式下才可以使用,通常用来检测传入参数。assert判断条件为假时,会终止程序运行,并且报错。

attribute的使用

attribute((at(地址)))绝对定位,绝对定位到flash,或者是ram里面
定位到flash,一般是固化一些信息的。
定位到ram,比如定义一个数组,缓冲较大的数据或者是做内存池的时候,需要从固定的内存的位置,开始分配内存。
attribute((packed)) 取消优化编译对其。
attribute((aligned(n))) 按多少个字节对其。
attribute((weak)) 库函数里面使用,如果用户定义了,就是用用户的,否则使用默认定义的。

volatile的含义

去掉编译优化,每次都都会从对应的地址里面拿到对应的数据,类似于指针变量。

const 的使用

只读,可以用来修饰,变量,数组,指针,函数的形参,函数的返回值。
需要注意的是使用const修饰函数的时候,如果函数的返回值,是指针变量。那么表示函数返回值指针,指向的内容是只读的。当这个函数作为右值的时候,左值的类型必须也是只读的,否则就会报警。

main.c: In function ‘main’:
main.c:14:11: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
  int *p = fun1(a);

main.c: In function ‘main’:
main.c:14:11: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
int *p = fun1(a);

大小端和联合体

 14     union                                                                                           
 15     {                                                                                               
 16         int i;                                                                                      
 17         char a[2];                                                                                  
 18     }*p, u;                                                                                         
 19                                                                                                     
 20     u.i = 0;                                                                                                                                                                              
 21     p = &u;                                                                                         
 22     p->a[0] = 0x39;                                                                                 
 23     p->a[1] = 0x38;                                                                                 
 24                                                                                                     
 25     printf("p->i:%x\n", p->i);  

结果为:
p->i:3839

有用的宏

  1. offsetof介绍
    知道结构体类型,和结构体成员,就可以知道该成员在结构体中的偏移。
    #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
    说明:获得结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量。
    (01) ( (TYPE *)0 ) 将零转型为TYPE类型指针,即TYPE类型的指针的地址是0。
    (02) ((TYPE *)0)->MEMBER 访问结构中的数据成员。
    (03) &( ( (TYPE )0 )->MEMBER ) 取出数据成员的地址。由于TYPE的地址是0,这里获取到的地址就是相对MEMBER在TYPE中的偏移。
    (04) (size_t)(&(((TYPE
    )0)->MEMBER)) 结果转换类型。对于32位系统而言,size_t是unsigned int类型;对于64位系统而言,size_t是unsigned long类型。

  2. container_of介绍
    知道结构体类型,结构体成员,结构体成员地址,就可以知道结构体的首地址
    #define container_of(ptr, type, member) ({
    const typeof( ((type *)0)->member ) *__mptr = (ptr);
    (type *)( (char *)__mptr - offsetof(type,member) );})
    说明:根据"结构体(type)变量"中的"域成员变量(member)的指针(ptr)“来获取指向整个结构体变量的指针。
    (01) typeof( ( (type *)0)->member ) 取出member成员的变量类型。
    (02) const typeof( ((type *)0)->member ) *__mptr = (ptr) 定义变量__mptr指针,并将ptr赋值给__mptr。经过这一步,__mptr为member数据类型的常量指针,其指向ptr所指向的地址。
    (04) (char *)__mptr 将__mptr转换为字节型指针。
    (05) offsetof(type,member)) 就是获取"member成员"在"结构体type"中的位置偏移。
    (06) (char *)__mptr - offsetof(type,member)) 就是用来获取"结构体type"的指针的起始地址(为char *型指针)。
    (07) (type *)( (char *)__mptr - offsetof(type,member) ) 就是将"char *类型的结构体type的指针"转换为"type *类型的结构体type的指针”。

可变参数函数

定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
使用宏 va_end 来清理赋予 va_list 变量的内存。

#include <stdio.h>
#include <stdarg.h>
 
double average(int num,...)
{
 
    va_list valist;
    double sum = 0.0;
    int i;
 
    /* 为 num 个参数初始化 valist */
    va_start(valist, num);
 
    /* 访问所有赋给 valist 的参数 */
    for (i = 0; i < num; i++)
    {
       sum += va_arg(valist, int);
    }
    /* 清理为 valist 保留的内存 */
    va_end(valist);
 
    return sum/num;
}
 
int main()
{
   printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

全局变量缺点:

1.全局变量存在周周期长,将会占用更多的内存。
2.全局变量多处被调用,使全局变量的值变得不确定。
(1)用全局变量会增加程序的耦合性,会有一定移植麻烦,代码重用率低。
(2)全局变量,使用和更改他的地方多了以后,各个地方逻辑关系难以确定。
(3)过多的全局变量,大大降低程序的可读性,可维护性。
(4)容易造成命名冲突

字符串处理函数

字符串复制: strcpy();
字符串比较: strcmp();
字符串拼接: strcat();
查询字串: strchr();
查询子串: strstr();
字符串比较:memcmp();

输入输出函数

打印函数

打印的格式:

运算符的优先级如何确定

有用的网站
https://blog.csdn.net/wangkaiblog/article/details/7720454

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值