[C.C++] 【C语言编程指南】

403 0
Honkers 2025-4-16 12:52:14 来自手机 | 显示全部楼层 |阅读模式

■ C语言基础

推荐网页
https://www.cnblogs.com/shuopython/p/15270897.html

■ 数据类型

■ 数据类型

■ 数据类型占位符


%d------整型
%f------浮点数
输入%lf输出%f------高精度浮点数(比较特殊)
%s------字符串
%c------字符
%p------指针
%e------科学计数法
%g------小数或科学计数法

%o 以八进制输出
%x 以16进制输出
%i 结构体输出

%d 整型int
%ld 长整型long
%lld 长长整型long long (int)
%hd 短整型short int

%u 无符号整型unsigned int
%hu 无符号短整型unsigned short int
%lu 无符号长整形unsigned long int
%llu 无符号长长整型unsigned long long

%f 浮点型float
输入%lf,输出%f 高精度浮点型double
%e(E) 以指数形式表示的浮点型
%m.nf 可控制输出小数位数

如何控制输出的格式%m.nf
%.2f输出两位小数,左对齐输出,位数不够自动补0;

%02d 右对齐输出,位数不够补0;
%2d右对齐输出,位数不够补/0;

  1. #include <stdio.h>
  2. int main()
  3. {
  4. printf("%09d\n",2345);
  5. printf("%9d\n",2345);
  6. printf("%.2f\n",2345.235);
  7. printf("%.2d\n",2345.235);
  8. return 0;
  9. }
  10. 输出结果:
  11. 000002345
  12. 2345
  13. 2345.24
  14. 1537761952 //这个printf的函数我丢给他一个浮点数,却要他输出一个%.2d,是不是输出错误了,所以平时要保持这优秀而严谨的习惯。
复制代码

■ 数字后面带个U,L,F的含义

U 表示该常数用无符号整型方式存储,相当于 unsigned int
L 表示该常数用长整型方式存储,相当于 long
F 表示该常数用浮点方式存储,相当于 float三、自动类型转换

(“L”和“l” 数值后面加“L”和“l”(小写的l)的意义是该数值是long型。)

数值后面加“”H“、“h”的意义是该数值是用16进制表示的。
数值后面加“”B“、“b”的意义是该数值是用2进制表示的。
后面什么也不加,代表10进制。

例如:
5L 的数据类型为long int。
5.12L 的数据类型为long double。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. printf("%d %d %d\n", sizeof(1.1), sizeof(1.1f), sizeof(1.1l));
  5. printf("%d %d %d\n", sizeof(1), sizeof(1l), sizeof(1ll));
  6. printf("%d %d\n", sizeof(0xFF), sizeof(0xFFu));
  7. printf("%d %d\n", sizeof(0xFFFFFFFFFFFFFFFFLL), sizeof(0xFFFFFFFFFFFFFFFF));
  8. if (-2 + 1u >= 0)
  9. printf("???\n");
  10. else
  11. printf("...\n");
  12. if (-2 + 1 >= 0)
  13. printf("???\n");
  14. else
  15. printf("...\n");
  16. return 0;
  17. }
  18. 结果:
  19. 8 4 12
  20. 4 4 8
  21. 4 4
  22. 8 8
  23. ???
  24. ...
复制代码

■ 自动类型转换

■ 异或

  1. 原则:1和1取0,0和0 取0,1和0 取1.
  2. 作用:交换两个整数的值而不必用第三个参数
  3. a = 9;
  4. b = 11;
  5. a=a^b; 1001^1011=0010
  6. b=b^a; 1011^0010=1001
  7. a=a^b; 0010^1001=1011
复制代码

■ 关键字

设置表格对齐方式:

关键字描述
__asm用于内嵌汇编代码和直接访问底层硬件
__inline用于内联函数,以减少函数调用的开销
attribute用于定义变量或函数的属性,例如对齐方式、可见性、优化等级等
__packed用于取消结构体成员的对齐,以节省内存空间
__irq, __fiq用于定义中断服务函数的类型,以区分普通函数和中断函数
register用于将变量声明为寄存器变量,以提高访问速度
volatile告诉编译器该变量可能会被意外修改,从而避免编译器对该变量进行优化
__weak, __strong用于声明弱引用和强引用变量,在链接时进行符号重定向
__packed, attribute((aligned(n)))用于控制结构体和变量的对齐方式
ARM_ARCH_XX用于条件编译,根据处理器的不同选择不同的代码路径

■ const

区分常量指针和指针常量的关键就在于星号的位置,
我们以星号为分界线,如果const在星号的左边,则为常量指针,如果const在星号的右边则为指针常量。
如果我们将星号读作‘指针’,将const读作‘常量’的话,内容正好符合。

int const * n;是常量指针,
const int * n;是常量指针,
int *const n; 是指针常量。

属性描述书写注意点
常量指针是指针指向的内容是常量const int * n;
int const * n;
int a=5;
int b=6;
const int* n=&a;
n=&b;// 不能通过n改变a的值但是可以改变n的指向。
指针常量是指指针本身是个常量,不能在指向其他的地址int *const n;int a=5;
int *p=&a;
int * const n=&a;
*p=8;
属性描述
const char *p;const 修饰的是 *p 所以是,*p值不能更改。 p[1] = ‘W’; 错误
char const *p;const 修饰的是 *p 所以是,*p值不能更改。 p[1] = ‘W’; 错误
char * const p;const 修饰的是 p, 所以是 p指针不能更改,*p值可变。 p=&str[1]; 错误。
  1. char str[10]="Hello";
  2. char buf[10]="World";
  3. //const char *p=str; //常量指针 P = buf; 正确, p[1] = 'W'; 错误
  4. //char const *p=str; //常量指针 P = buf; 正确, p[1] = 'W'; 错误
  5. char *const p=str; //指针常量 p[0]='X'; 正确, p = buf; 错误 , p=&str[1]; 错误。
复制代码

■ extern

■ typedef

作用是为一种数据类型定义一个新的名字。对于以上两种结构体定义形式,
typedef都可以为其创建别名。

  1. 1. 先定义结构体类型,再定义结构体变量
  2. struct student{
  3. char no[20]; //学号
  4. char name[20]; //姓名
  5. char sex[5]; //性别
  6. int age; //年龄
  7. };
  8. struct student stu1,stu2;
  9. //此时stu1,stu2为student结构体变量
  10. 2. 定义结构体类型的同时定义结构体变量。
  11. struct student{
  12. char no[20]; //学号
  13. char name[20]; //姓名
  14. char sex[5]; //性别
  15. int age; //年龄
  16. } stu1,stu2;
  17. 此时还可以继续定义student结构体变量如:
  18. struct student stu3;
  19. 1. 先定义结构体类型,再定义结构体变量
  20. struct{
  21. char no[20]; //学号
  22. char name[20]; //姓名
  23. char sex[5]; //性别
  24. int age; //年龄
  25. } stu1,stu2;
复制代码

■ static

■ 左值和右值

■ 位域

C 语言的位域(bit-field)是一种特殊的结构体成员,允许我们按位对成员进行定义,指定其占用的位数。
用 1 位二进位存放一个开关量时,只有 0 和 1 两种状态。

实例 1 带有预定义宽度的变量被称为位域

  1. struct bs{
  2. int a:8;
  3. int b:2;
  4. int c:6;
  5. }data;
  6. struct bs 的结构体,data 为 bs 的结构体变量,共占四个字节
  7. 对于位域来说,它们的宽度不能超过其数据类型的大小
复制代码

实例 2 位数

  1. #include <stdio.h>
  2. struct packed_struct {
  3. unsigned int f1 : 1; // 1位的位域
  4. unsigned int f2 : 1; // 1位的位域
  5. unsigned int f3 : 1; // 1位的位域
  6. unsigned int f4 : 1; // 1位的位域
  7. unsigned int type : 4; // 4位的位域
  8. unsigned int my_int : 9; // 9位的位域
  9. };
  10. int main() {
  11. struct packed_struct pack;
  12. pack.f1 = 1;
  13. pack.f2 = 0;
  14. pack.f3 = 1;
  15. pack.f4 = 0;
  16. pack.type = 7;
  17. pack.my_int = 255;
  18. printf("f1: %u\n", pack.f1);
  19. printf("f2: %u\n", pack.f2);
  20. printf("f3: %u\n", pack.f3);
  21. printf("f4: %u\n", pack.f4);
  22. printf("type: %u\n", pack.type);
  23. printf("my_int: %u\n", pack.my_int);
  24. return 0;
  25. }
  26. 输出结果为:
  27. f1: 1
  28. f2: 0
  29. f3: 1
  30. f4: 0
  31. type: 7
  32. my_int: 255
复制代码

实例 3 空域

  1. struct bs{
  2. unsigned a:4;
  3. unsigned :4; /* 空域 */
  4. unsigned b:4; /* 从下一单元开始存放 */
  5. unsigned c:4
  6. }
复制代码

实例 3 超过位数赋值

  1. #include <stdio.h>
  2. #include <string.h>
  3. struct
  4. {
  5. unsigned int age : 3;
  6. } Age;
  7. int main( )
  8. {
  9. Age.age = 4;
  10. printf( "Sizeof( Age ) : %d\n", sizeof(Age) );
  11. printf( "Age.age : %d\n", Age.age );
  12. Age.age = 7;
  13. printf( "Age.age : %d\n", Age.age );
  14. Age.age = 8; // 二进制表示为 1000 有四位,超出
  15. printf( "Age.age : %d\n", Age.age );
  16. return 0;
  17. }
  18. 当上面的代码被编译时,它会带有警告,当上面的代码被执行时,它会产生下列结果:
  19. Sizeof( Age ) : 4
  20. Age.age : 4
  21. Age.age : 7
  22. Age.age : 0
复制代码

实例 4 结构体和位域对比

  1. #include <stdio.h>
  2. #include <string.h>
  3. /* 定义简单的结构 */
  4. struct
  5. {
  6. unsigned int widthValidated;
  7. unsigned int heightValidated;
  8. } status1;
  9. /* 定义位域结构 */
  10. struct
  11. {
  12. unsigned int widthValidated : 1;
  13. unsigned int heightValidated : 1;
  14. } status2;
  15. int main( )
  16. {
  17. printf( "Memory size occupied by status1 : %d\n", sizeof(status1));
  18. printf( "Memory size occupied by status2 : %d\n", sizeof(status2));
  19. return 0;
  20. }
  21. 它会产生下列结果:
  22. Memory size occupied by status1 : 8
  23. Memory size occupied by status2 : 4
复制代码

实例 5 可以对 u16Bit;数据类型中的每个位进行操作赋值,u16Bit.b1 = 1;

  1. typedef struct{
  2. union{
  3. struct {
  4. uint8_t b0:1;
  5. uint8_t b1:1;
  6. uint8_t b2:1;
  7. uint8_t b3:1;
  8. uint8_t b4:1;
  9. uint8_t b5:1;
  10. uint8_t b6:1;
  11. uint8_t b7:1;
  12. uint8_t b8:1;
  13. uint8_t b9:1;
  14. uint8_t b10:1;
  15. uint8_t b11:1;
  16. uint8_t b12:1;
  17. uint8_t b13:1;
  18. uint8_t b14:1;
  19. uint8_t b15:1;
  20. };
  21. uint16_t byte;
  22. };
  23. }u16Bit;
  24. /*********************************定义uint32_t*******************************/
  25. typedef struct {
  26. uint32_t b0:1;
  27. }u200Bit;
  28. u200Bit u200bit_t;
  29. u200bit_t.b0=1;//赋值
  30. qDebug() << "sizeof(u200Bit)=" << sizeof(u200Bit) << u200bit_t.b0 << endl; //sizeof(u200Bit)= 4 1
  31. /*********************************定义uint16_t*******************************/
  32. typedef struct {
  33. uint16_t b0:1;
  34. }u200Bit;
  35. u200Bit u200bit_t;
  36. u200bit_t.b0=1;//赋值
  37. qDebug() << "sizeof(u200Bit)=" << sizeof(u200Bit) << u200bit_t.b0 << endl; //sizeof(u200Bit)= 2 1
  38. /*********************************定义uint8_t*******************************/
  39. typedef struct {
  40. uint8_t b0:1;
  41. }u200Bit;
  42. u200Bit u200bit_t;
  43. u200bit_t.b0=1; //赋值
  44. qDebug() << "sizeof(u200Bit)=" << sizeof(u200Bit) << u200bit_t.b0 << endl;//sizeof(u200Bit)= 1 1
复制代码

■ float数据存储

  1. float类型数字在计算机中用4个字节存储。遵循IEEE-754格式标准:
  2. 一个浮点数有2部分组成:底数m和指数e
  3. 底数部分 使用二进制数来表示此浮点数的实际值
  4. 指数部分 占用8bit的二进制数,可表示数值范围为0-255
  5. 但是指数可正可负,所以,IEEE规定,此处算出的次方必须减去127才是真正的指数。
  6. 所以,float类型的指数可从-126到128
  7. 底数部分实际是占用24bit的一个值,但是最高位始终为1,所以,最高位省去不存储,在存储中占23bit
  8. 科学计数法。
  9. 格式:
  10. SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM
  11. S表示浮点数正负
  12. E指数加上127后的值得二进制数据
  13. M底数
  14. 举例:
  15. 17.625在内存中的存储
  16. 首先要把17.625换算成二进制:10001.101
  17. 整数部分,除以2,直到商为0,余数反转。
  18. 小数部分,乘以2,直到乘位0,进位顺序取。
  19. 在将10001.101右移,直到小数点前只剩1位:
  20. 1.0001101 * 2^4 因为右移动了四位
  21. 这个时候,我们的底数和指数就出来了
  22. 底数:因为小数点前必为1,所以IEEE规定只记录小数点后的就好。所以,此处的底数为:0001101
  23. 指数:实际为4,必须加上127(转出的时候,减去127),所以为131。也就是10000011
  24. 符号部分是整数,所以是0
  25. 综上所述,17.625在内存中的存储格式是:
  26. 01000001 10001101 00000000 00000000
复制代码

■ .h文件

  1. #ifndef _VERSION_H_
  2. #define _VERSION_H_
  3. ....
  4. #endif
复制代码

■ 条件编译

1、#if:如果条件为真,则执行相应的操作。
2、#elif:类似于 elseif 的用法,当前面条件为假,再判断该条件是否为真,如果是真,则执行相应操作。
3、#else:如果前面所有条件均为假,则执行相应操作。
4、#ifdef:如果该宏已定义,则执行相应操作。
5、#ifndef:如果该宏没有定义,则执行相应操作。
6、#endif :结束对应的条件编译指令。(不能省略)

■ #define

  1. //--------------------------------------------------------
  2. #define CFDA
  3. #ifdef CFDA
  4. //代码区 有定义CFDA此处高亮,没有定义灰显。
  5. #endif
  6. 不带替换体
  7. //--------------------------------------------------------
  8. #define VERSION_GHK 2
  9. #define __ARMCLIB_VERSION 5060037
  10. 带替换体
  11. //--------------------------------------------------------
  12. #define SQUARE(x) ((x)*(x))
  13. 带参数 输出结果 printf("%d\n",SQUARE(8)); = 8*8 = 64
  14. //--------------------------------------------------------
  15. #define DEBUG_PRINT printf("file:%s\tline:%d\t \
  16. date:%s\ttime:%s\n" ,\
  17. __FILE__,__LINE__ , \
  18. __DATE__,__TIME__ )
  19. 加换行符
  20. //--------------------------------------------------------
  21. #define XVALUE(n) x##n //将符号 x 和 n 合并为一个记号
  22. #define YVALUE(n) y##n //将符号 y 和 n 合并为一个记号
  23. #define ZVALUE(n) z##n //将符号 z 和 n 合并为一个记号
  24. int XVALUE(1) = 10; //等价 int x1 = 10
  25. int YVALUE(1) = 10; //等价 int y1 = 10
  26. int ZVALUE(1) = 10; //等价 int z1 = 10
  27. #define NUM(a,b,c) a##b##c
  28. printf("NUM(1,2,3) = %d\n",NUM(1,2,3)); //NUM(1,2,3) = 123
  29. 添加拼接
  30. //--------------------------------------------------------
  31. //--------------------------------------------------------
复制代码

拼接和define

■ #ifdef,#ifndef,#else与#endif

#ifdef 用于判断某个宏是否定义,
#ifndef 功能正好相反,二者仅支持判断单个宏是否已经定义
语法
#ifdef 标识符
#ifndef 标识符

  1. ifdef _DEBUG
  2. // ... do some operations
  3. #endif
  4. #ifdef _WIN32
  5. // ... use Win32 API
  6. #endif
  7. //---------------------------------
  8. #ifdef ABC
  9. // ... codes while definded ABC
  10. #elif (CODE_VERSION > 2)
  11. // ... codes while CODE_VERSION > 2
  12. #else
  13. // ... remained cases
  14. #endif // #ifdef ABC
  15. //---------------------------------
复制代码

■ #if,#elif,#else与#endif

  1. #if 常量表达式1
  2. // ... some codes
  3. #elif 常量表达式2
  4. // ... other codes
  5. #elif 常量表达式3
  6. // ...
  7. ...
  8. #else
  9. // ... statement
  10. #endif
  11. //---------------------------------
  12. #if 0
  13. //代码1
  14. #endif
  15. 相当于#if 0 和 #endif之间的代码被注释掉了,一般在IDE环境中会显示灰色。
  16. //---------------------------------
  17. #define FUNCTION 0
  18. #if FUNCTION
  19. //代码1
  20. #endif
  21. 即FUNCTION宏定义为0或者1,决定了#if FUNCTION和#endif之间的代码是否参与编译。
  22. //---------------------------------
  23. #define FUNCTION 0
  24. #if (FUNCTION == 0)
  25. //代码1
  26. #elif (FUNCTION == 1)
  27. //代码2
  28. #elif (FUNCTION == 2)
  29. //代码3
  30. #endif
  31. FUNCTION宏定义为0或者1或者2,FUNCTION为0,则代码1参与编译,FUNCTION为1,则代码2参与编译,FUNCTION为2,则代码3参与编译。
  32. //---------------------------------
  33. #define FUN_A 1
  34. #define FUN_B 1
  35. #if (FUN_A && FUN_B)
  36. //代码1
  37. #endif
  38. 只有当FUN_A和FUN_B同时定义为1时,代码1才参与编译,否则代码1不参与编译。
  39. //---------------------------------
复制代码

■ #if defined 和 #if !defined

defined用来测试某个宏是否被定义。
defined(name): 若宏被定义,则返回1,否则返回0。
它与#if、#elif、#else结合使用来判断宏是否被定义,乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef。defined可用于在一条判断语句中声明多个判别条件;#ifdef和#ifndef则仅支持判断一个宏是否定义。

  1. //---------------------------------
  2. 判断多个条件
  3. #if defined(VAX) && defined(UNIX) && !defined(DEBUG)
  4. //---------------------------------
  5. #if defined(VAX)
  6. #ifdef VAX
  7. 如果是判断单个条件,#if defined和#ifdef没有啥差别。
  8. //---------------------------------
  9. #if !defined(VAX)
  10. #ifndef VAX
  11. 说明 #if !defined的本质还是 #if defined,所以它们可以组合。
复制代码

■ #和##

#把一个宏参数变成对应的字符串。
##运算符 ;与 #运算符类似,
##运算符可用于类函数宏(带参宏)的替换部分。
##运算符可以把两个记号组合成一个记号。

  1. #define XVALUE(n) x##n //将符号 x 和 n 合并为一个记号
  2. #define YVALUE(n) y##n //将符号 y 和 n 合并为一个记号
  3. #define ZVALUE(n) z##n //将符号 z 和 n 合并为一个记号
  4. int XVALUE(1) = 10; //等价 int x1 = 10
  5. int YVALUE(1) = 10; //等价 int y1 = 10
  6. int ZVALUE(1) = 10; //等价 int z1 = 10
  7. #define NUM(a,b,c) a##b##c
  8. printf("NUM(1,2,3) = %d\n",NUM(1,2,3)); //NUM(1,2,3) = 123
复制代码

#示例

attribute

attribute 关键字用来声明一个函数、变量或类型的特殊属性。
申明这些属性主要用途就是指导编译程序进行特定方面的优化或代码检查。
attribute((ATTRIBUTE))
attribute 后面是两对小括号,不能图方便只写一对,否则会编译报错。
ATTRIUBTE 表示要声明的属性,目前支持十几种属性声明:

  • section:自定义段
  • aligned:对齐
  • packed:对齐
  • format:检查函数变参格式
  • weak:弱声明
  • alias:函数起别名
  • noinline:无内联
  • always_inline:内联函数总是展开

对一个变量也可以同时添加多个属性。在定义变量前,各个属性之间用逗号隔开。

例如:
char c attribute((packed,algined(4)));
char c attribute((packed,algined(4)))=4;
attribute((packed,algined(4)))charc=4;

■ 属性声明:section

section 属性的主要作用是:在程序编译时,将一个函数或者变量放到指定的段,即指定的section 中。

一个可执行文件注意由代码段,数据段、BSS 段构成。
代码段主要用来存放编译生成的可执行指令代码、数据段和BSS段用来存放全局变量和未初始化的全局变量。
除了这三个段,可执行文件还包含一些其他的段。我们可以用 readelf 去查看一个可执行文件各个section信息。

attribute ((section(“xxx”))),修改段的属性。

示例一:

  1. intglobal_val=0;
  2. int unint_val __attribute__((section(".data"))); //unint_val 这个变量,已经被编译器放在数据段中。当然也可以自定义段的名称。
  3. intmain()
  4. {
  5. return0;
  6. }
复制代码

■ 属性声明:aligned

GNU C 通过 attribute 来声明 aligned 和 packed 属性,指定一个变量或类型的对齐方式。

■ aligned(4)-地址对齐

aligned 属性,我们可以显示地指定变量 a 在内存中的地址对齐方式。
aligned 有一个参数,表示要按几个字节对齐,使用时要注意,地址对齐的字节数必须是 2 的幂次方,否则编译就会报错。

  1. #include <stdio>
  2. int a=1;
  3. int b=2;
  4. char c1=3;
  5. char c2=4; //替换成 char c2 __attribute__((aligned(4)))=4;
  6. int main()
  7. {
  8. printf("a=%p",&a);
  9. printf("b=%p",&b);
  10. printf("c1=%p",&c1);
  11. printf("c2=%p",&c2);
  12. return0;
  13. }
  14. 可以看到,char 占一个字节,c2的地址紧挨着 c1
  15. a=0x404030
  16. b=0x404034
  17. c1=0x404038
  18. c2=0x404039
  19. 使用 aligned 地址对齐
  20. a=0x404030
  21. b=0x404034
  22. c1=0x404038
  23. c2=0x40403c
  24. 可以看到,c2 的地址是按照4字节对齐
复制代码

■ 结构体对齐

结构体作为一种复杂的数据类型,编译器在给一个结构体变量分配存储空间时,不仅要考虑结构体内各个成员的对齐,还要考虑结构体整体的对齐。

编译器一定会按照 aligend 指定的方式对齐吗?
我们通过这个属性声明,其实只是建议编译器按照这种大小地址对齐,但不能超过编译器允许的最大值。一个编译器,对每个基本的数据类型都有默认的最大边界对齐字节数,如果超过了,则编译器只能按照它规定的最大对齐字节数来对变量分配地址。

  1. #include <stdio>
  2. struct data{
  3. char a;
  4. int b;
  5. short c;
  6. };
  7. intmain()
  8. {
  9. struct data s;
  10. printf("size=%d",sizeof(s));
  11. printf("a=%p",&s.a);
  12. printf("b=%p",&s.b);
  13. printf("c=%p",&s.c);
  14. return0;
  15. }
  16. 四字节对齐:占12字节
  17. size=12
  18. a=0xffb6c374
  19. b=0xffb6c378
  20. c=0xffb6c37c
  21. ---------------------------------------------------------------------------------------------------------
  22. 结构体成员顺序不同,所占大小有可能不同:
  23. structdata{
  24. char a;
  25. short b;
  26. int c;
  27. };
  28. 四字节对齐:占8字节
  29. size=8
  30. a=0xffa2d9f8
  31. b=0xffa2d9fa
  32. c=0xffa2d9fc
  33. ---------------------------------------------------------------------------------------------------------
  34. 显示的指定成员的对齐方式:
  35. #include <stdio>
  36. struct data{
  37. char a;
  38. short b __attribute__((aligned(4)));
  39. int c;
  40. };
  41. int main()
  42. {
  43. struct data s;
  44. printf("size=%d ",sizeof(s));
  45. printf("a=%p ",&s.a);
  46. printf("b=%p ",&s.b);
  47. printf("c=%p ",&s.c);
  48. return0;
  49. }
  50. 四字节对齐:占12字节
  51. size=12
  52. a=0xffb6c374
  53. b=0xffb6c378
  54. c=0xffb6c37c
  55. ---------------------------------------------------------------------------------------------------------
  56. 显示指定结构体对齐方式:
  57. #include
  58. struct data{
  59. char a;
  60. short b;
  61. int c;
  62. }__attribute__((aligned(16)));
  63. int main()
  64. {
  65. struct data s;
  66. printf("size=%d",sizeof(s));
  67. printf("a=%p",&s.a);
  68. printf("b=%p",&s.b);
  69. printf("c=%p",&s.c);
  70. return0;
  71. }
  72. 16字节对齐,末尾填充8字节:占16字节
  73. size=16
  74. a=0xffa2d9f8
  75. b=0xffa2d9fa
  76. c=0xffa2d9fc
复制代码

■ 属性声明:packed

aligned 属性一般用来增大变量的地址对齐,元素之间地址对齐会造成一定的内存空洞,而packed属性则正好相反,一般用来减少地址对齐,指定变量或类型使用最可能小的地址对齐方式。

  1. #include
  2. struct data{
  3. char a;
  4. short b __attribute__((packed));
  5. int c __attribute__((packed));
  6. };
  7. intmain()
  8. {
  9. struct data s;
  10. printf("size=%d",sizeof(s));
  11. printf("a=%p",&s.a);
  12. printf("b=%p",&s.b);
  13. printf("c=%p",&s.c);
  14. return 0;
  15. }
  16. 使用最小一字节对齐:
  17. size=7
  18. a=0xfff38fb9
  19. b=0xfff38fba
  20. c=0xfff38fbc
  21. ---------------------------------------------------------------------------------------------------------
  22. 对整个结构体添加packed属性
  23. structdata{
  24. char a;
  25. short b;
  26. int c;
  27. }__attribute__((packed));
  28. 内核中的packed、aligned 声明
  29. 在内核源码中,我们经常看到aligned 和 packed 一起使用,即对一个变量或者类型同时使用packed 和 aligned 属性声明。
  30. 这样做的好处是即避免了结构体各成员间地址对齐产生的内存空洞,又指定了整个结构体的对齐方式。
  31. struct data{
  32. char a;
  33. short b;
  34. int c;
  35. }__attribute__((packed,aligned(8)));
复制代码

■ 属性声明:format

format 属性用来指定变参函数的参数格式检查。

使用方法:
attribute((format (archetype, string-index, frist-to-check)))

示例:
void LOG(const char fmt, …) attribute((format(printf,1,2)));
LOG(“hello world ,i am %d ages \n”, age); /
前者表示格式字符串,后者表示所有的参数*/

属性format(printf,1,2) 有3个参数,
第一个参数pritnf 是告诉编译器,按照printf的标准来检查;
第二个参数表示LOG()函数所有的参数列表中格式字符串的位置索引,即第一个参数"hello world ,i am %d ages \n";
第三个参数是告诉编译器要检查的参数的起始位置,即从第二个参数age开始检查。

■ 属性声明:weak

GNU C 通过 weak 属性声明,将一个强符号,转换为弱符号。
使用方法如下:

  1. void __attribute__((weak)) func(void);
  2. int num __attribute__((weak));
复制代码

强符号:函数名,初始化的全局变量名
弱符号:未初始化的全局变量名

■ 属性声明:alias

alias 属性主要用来给函数定义一个别名

示例

  1. void __f(void)
  2. {
  3. printf("__f\n");
  4. }
  5. void f(void) __attribute__((alias("__f")));
  6. int main(void)
  7. {
  8. f();
  9. return 0;
  10. }
复制代码

在Linux 内核中你会发现alias有时候会和weak属性一起使用。如有些接口随着内核版本升级,函数接口发生了变化,我们可以通过alias属性对旧的接口名字进行封装,重新起一个接口名字。

  1. //f.c
  2. void __f(void)
  3. {
  4. printf("__f\n");
  5. }
  6. void f() __attribute__((weak, alias("__f")));
  7. //main.c
  8. void __attribute__((weak)) f(void);
  9. void f(void)
  10. {
  11. printf("f\n");
  12. }
  13. int main()
  14. {
  15. f();
  16. return 0;
  17. }
  18. 如果main.c 中定义了f()函数,那么main 函数调用f()会调用新定义的函数(强符号),否则调用__f()函数(弱符号)
复制代码

■ 属性声明:noinline

static inline attribute((noinline)) void f()

■ 属性声明:always_inline

static inline attribute((always_inline)) void f()

■ 属性声明: attribute((used))

表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。

■ 属性声明: attribute((unused))

向编译器说明这段代码有用,即使在没有用到的情况下编译器也不会警告!

■ 字符串

字符串实际上是使用空字符 \0 结尾的一维字符数组。
\0 是用于标记字符串的结束。
空字符(Null character)又称结束符 缩写 NULL,是一个数值为 0 的控制字符,
\0 是转义字符,意思是告诉编译器,这不是字符 0,而是空字符。
‘\0’ == NULL == 0(数字0)


两个char 数组 第一个没有\0情况下后,printf会打印第二个数组内容。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. char site[6] = {'R','u','n','o','b','9'};
  5. char sizzz[7]={'l','t','d','s','\0'};
  6. printf("%s\n",site); //Runob9ltds site数组没有\0 输出了sizzz内容。
  7. site[3]=0;
  8. printf("%s\n",site); //Run 遇见0就停止
  9. printf("%d,%d\n",site[4],site[5]); //98,57
  10. }
  11. pengyc@tengmai:~/DockPro/demo$ ./a.out
  12. Runob9ltds
  13. Run
  14. 98,57
复制代码

char sizeee[] = “abcde”; 大小

  1. char sizeee[] = "abcde";
  2. printf("%ld\n",sizeof(sizeee)); //6 说明大小会在后面加\0;
复制代码

■ C指针

指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。

■ 指针介绍

■ 指针的类型

从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。

  1. (1) int*ptr; //指针的类型是int*
  2. (2) char*ptr; //指针的类型是char*
  3. (3) int**ptr; //指针的类型是int**
  4. (4) int(*ptr)[3]; //指针的类型是int(*)[3]
  5. (5) int*(*ptr)[4];//指针的类型是int*(*)[4]
复制代码

■ 指针所指向的类型

当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。

  1. (1) int*ptr;//指针所指向的类型是int
  2. (2) char*ptr;//指针所指向的的类型是char
  3. (3) int**ptr;//指针所指向的的类型是int*
  4. (4) int(*ptr)[3];//指针所指向的的类型是int()[3]
  5. (5) int*(*ptr)[4];//指针所指向的的类型是int*()[4]
复制代码

■ 指针的值,或者叫指针所指向的内存区或地址

指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。
在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。
指针的值是这块内存区域的首地址.
指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。

■ 指针本身所占据的内存区

指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。
指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。

■ 指针的算术运算

指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的

  1. ------------------------------------------------------------
  2. 例二:
  3. char a[20];
  4. int*ptr=a;
  5.   ...
  6.   ...
  7. ptr++;
  8. 指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整形变量a。
  9. 接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被加上了4。
  10. 由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。
  11. 由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了数组a中从第4号单元开始的四个字节。
  12. ------------------------------------------------------------
  13. 例三
  14. int array[20];
  15. int*ptr=array;
  16. for(i=0;i <20;i++)
  17. {
  18. (*ptr)++;
  19.   ptr++;
  20. }
  21. 这个例子将整型数组中各个单元的值加1。
  22. 由于每次循环都将指针ptr加1,所以每次循环都能访问数组的下一个单元。
  23. ------------------------------------------------------------
  24. 例四
  25. chara[20];
  26. int*ptr=a;
  27.   ...
  28.   ...
  29. ptr+=5;
  30. ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。
  31. ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。
复制代码

■ 运算符&和*

这里&是取地址运算符,是…书上叫做 "间接运算符 "。
 &a的运算结果是一个指针,指针的类型是a的类型加个
,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。
 p的运算结果就五花八门了。总之p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址是p所指向的地址。

  1. inta=12;
  2. intb;
  3. int*p;
  4. int**ptr;
  5. p=&a;
  6. //&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。
  7. *p=24;
  8. //*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a。
  9. ptr=&p;
  10. //&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int **。该指针所指向的类型是p的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。
  11. *ptr=&b;
  12. //*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用&b来给*ptr赋值就是毫无问题的了。
  13. **ptr=34;
  14. //*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果就是一个int类型的变量。
复制代码

■ 指针表达式

一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表式。

  1. 例六
  2. int a,b;
  3. intarray[10];
  4. int*pa;
  5. pa=&a; //&a是一个指针表达式。
  6. int**ptr=&pa; //&pa也是一个指针表达式。
  7. *ptr=&b; //*ptr和&b都是指针表达式。
  8. pa=array;
  9. pa++; //这也是指针表达式。
  10. 在例六中,&a不是一个左值,因为它还没有占据明确的内存。
  11. *ptr是一个左值,因为*ptr这个指针已经占据了内存,其实*ptr就是指针pa,既然pa已经在内存中有了自己的位置,那么*ptr当然也有了自己的位置。
  12. 例七:
  13. char*arr[20];
  14. char**parr=arr;//如果把arr看作指针的话,arr也是指针表达式
  15. char*str;
  16. str=*parr;//*parr是指针表达式
  17. str=*(parr+1);//*(parr+1)是指针表达式
  18. str=*(parr+2);//*(parr+2)是指针表达式
  19. 当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。
复制代码
  1. 例八:
  2. main()
  3. {
  4. int a[5]={1,2,3,4,5};
  5. int *ptr=(int *)(&a+1);
  6. printf("%d,%d",*(a+1),*(ptr-1));
  7. }
复制代码
  • &a + 1: 取数组a 的首地址,该地址的值加上sizeof(a) 的值,即&a + 5*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。
    (int *)(&a+1): 则是把上一步计算出来的地址,强制转换为int * 类型,赋值给ptr。

  • *(a+1): a,&a 的值是一样的,但意思不一样,a 是数组首元素的首地址,也就是a[0]的首地址,&a 是数组的首地址,a+1 是数组下一元素的首地址,即a[1]的首地址,&a+1 是下一个数组的首地址。所以输出 (ptr-1): 因为ptr 是指向a[5],并且ptr 是int * 类型,所以(ptr-1) 是指向a[4] ,输出5。

■ 函数传递二维数组

■ 形参给出第二维的长度。


■ 形参声明为指向数组的指针。

  1. #include <stdio.h>
  2. void func(int n, char (*str)[5] )
  3. {
  4. int i;
  5. for(i = 0; i < n; i++)
  6. printf("\nstr[%d] = %s\n", i, str[i]);
  7. }
  8. void main()
  9. {
  10. char* p[3];
  11. char str[][5] = {"abc","def","ghi"};
  12. func(3, str);
  13. }
复制代码

■ 形参声明为指针的指针。

  1. #include <stdio.h>
  2. void func(int n, char **str)
  3. {
  4. int i;
  5. for(i = 0; i < n; i++)
  6. printf("\nstr[%d] = %s\n", i, str[i]);
  7. }
  8. void main()
  9. {
  10. char* p[3];
  11. char str[][5] = {"abc","def","ghi"};
  12. p[0] = &str[0][0];
  13. p[1] = str[1];
  14. p[2] = str[2];
  15. func(3, p);
  16. }
复制代码





  1. char* arg[] = {
  2. "abc",
  3. "cde",
  4. "efg",
  5. };
  6. //这种写法和上面的等价
  7. char* b[3];
  8. b[0] = arg[0];
  9. b[1] = arg[1];
  10. b[2] = arg[2];
  11. int array[][3] = {
  12. {1, 2, 3},
  13. {2, 3, 4},
  14. {3, 4, 5},
  15. };
  16. int* a[3];
  17. a[0] = array[0];
  18. a[1] = array[1];
  19. a[2] = array[2];
复制代码

■ 二级指针传参数

■ 二级指针传参数,没有改变原始值

  1. uis@ubuntu:~/text$ gcc zhizheng.c
  2. uis@ubuntu:~/text$
  3. uis@ubuntu:~/text$ ./a.out
  4. &a=0x7ffe43789e08,&p=0x7ffe43789e0c
  5. pp=0x7ffe43789e08,kk=0x7ffe43789e0c---&pp=0x7ffe43789e10,&kk=0x7ffe43789e18
  6. x=0x7ffe43789e10,y=0x7ffe43789e18---&x=0x7ffe43789dd8,&y=0x7ffe43789dd0,--- *x=0x7ffe43789e08,*y=0x7ffe43789e0c
  7. x=0x7ffe43789e10,y=0x7ffe43789e18---&x=0x7ffe43789dd8,&y=0x7ffe43789dd0,--- *x=0x7ffe43789e08,*y=0x7ffe43789e0c
  8. x=0x7ffe43789e10,y=0x7ffe43789e18---&x=0x7ffe43789dd8,&y=0x7ffe43789dd0,--- *x=0x7ffe43789e0c,*y=0x7ffe43789e0c
  9. x=0x7ffe43789e10,y=0x7ffe43789e18---&x=0x7ffe43789dd8,&y=0x7ffe43789dd0,--- *x=0x7ffe43789e0c,*y=0x7ffe43789e08
  10. a = 20 b = 10, a=10,b=20
复制代码

■ 二级指针传参数,改变原始值

  1. uis@ubuntu:~/text$ gcc zhizheng2.c
  2. uis@ubuntu:~/text$
  3. uis@ubuntu:~/text$ ./a.out
  4. &a=0x7ffc22703778,&p=0x7ffc2270377c
  5. pp=0x7ffc22703778,kk=0x7ffc2270377c---&pp=0x7ffc22703780,&kk=0x7ffc22703788
  6. x=0x7ffc22703780,y=0x7ffc22703788---&x=0x7ffc22703748,&y=0x7ffc22703740,--- *x=0x7ffc22703778,*y=0x7ffc2270377c
  7. x=0x7ffc22703780,y=0x7ffc22703788---&x=0x7ffc22703748,&y=0x7ffc22703740,--- *x=0x7ffc22703778,*y=0x7ffc2270377c
  8. x=0x7ffc22703780,y=0x7ffc22703788---&x=0x7ffc22703748,&y=0x7ffc22703740,--- *x=0x7ffc22703778,*y=0x7ffc2270377c
  9. x=0x7ffc22703780,y=0x7ffc22703788---&x=0x7ffc22703748,&y=0x7ffc22703740,--- *x=0x7ffc22703778,*y=0x7ffc2270377c
  10. a = 20 b = 10, a=20,b=10
复制代码

■ 数组和指针

  1. 例八
  2. int array[10]={0,1,2,3,4,5,6,7,8,9},value;
  3. ...
  4. ...
  5. value=array[0];//也可写成:value=*array;
  6. value=array[3];//也可写成:value=*(array+3);
  7. value=array[4];//也可写成:value=*(array+4);
  8. 一般而言数组名array代表数组本身,类型是int[10],但如果把array看做指针的话,它指向数组的第0个单元,类型是int*,所指向的类型是数组单元的类型即int。
  9. 因此*array等于0就一点也不奇怪了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+3)等于3。
  10. 其它依此类推。
  11. 例九
  12. char* str[3]={
  13.   "Hello,thisisasample! ",
  14.   "Hi,goodmorning. ",
  15.   "Helloworld "
  16. };
  17. char s[80];
  18. strcpy(s,str[0]);//也可写成strcpy(s,*str);
  19. strcpy(s,str[1]);//也可写成strcpy(s,*(str+1));
  20. strcpy(s,str[2]);//也可写成strcpy(s,*(str+2));
  21. str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str当作一个指针的话,它指向数组的第0号单元,它的类型是char**,它指向的类型是char*。
  22. *str也是一个指针,它的类型是char*,它所指向的类型是char,它指向的地址是字符串 "Hello,thisisasample! "的第一个字符的地址,即 'H '的地址。 str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char*。
  23. *(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向 "Hi,goodmorning. "的第一个字符 'H ',等等
复制代码

■ 数组指针(也称行指针)

  1. 定义 int (*p)[n];
  2. ()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
  3. int a[3][4];
  4. int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
  5. p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
  6. p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
  7. 所以数组指针也称指向一维数组的指针,亦称行指针。
复制代码

■ 指针数组

  1. 定义 int *p[n];
  2. //这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
  3. []优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
  4. 如要将二维数组赋给一指针数组:
  5. int *p[3];
  6. int a[3][4];
  7. p++; //该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针
  8. for(i=0;i<3;i++)
  9. p[i]=a[i]
  10. 这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
  11. 所以要分别赋值。
复制代码

■ 指针变量

  1. int *p;
  2. *p = NULL;
  3. 同样,我们可以在编译器上调试这两行代码。第一行代码,定义了一个指针变量 p,其指向
  4. 的内存里面保存的是 int 类型的数据;但是这时候变量 p 本身的值是多少不得而知,也就是
  5. 说现在变量 p 保存的有可能是一个非法的地址。第二行代码,给*p 赋值为 NULL,即给 p
  6. 指向的内存赋值为 NULL;但是由于 p 指向的内存可能是非法的,所以调试的时候编译器可
  7. 能会报告一个内存访问错误。这样的话,我们可以把上面的代码改写改写,使 p 指向一块合
  8. 法的内存:
  9. int i = 10;
  10. int *p = &i;
  11. *p = NULL;
  12. 在编译器上调试一下,我们发现 p 指向的内存由原来的 10 变为 0 了;而 p 本身的值, 即内
  13. 存地址并没有改变
复制代码

■ 函数指针&指针函数

■ 指针函数


■ 函数指针

■ 可变参数

int func_name(int arg1, …); //省略号 … 表示可变参数列表。
函数 func() 最后一个参数写成省略号,即三个点号(…),省略号之前的那个参数是 int,代表了要传递的可变参数的总数

需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。
具体步骤如下:

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

常用的宏有:

  • va_start(ap, last_arg):初始化可变参数列表。
    ap 是一个 va_list 类型的变量,
    last_arg 是最后一个固定参数的名称(也就是可变参数列表之前的参数)。
    该宏将 ap 指向可变参数列表中的第一个参数。

  • va_arg(ap, type):获取可变参数列表中的下一个参数。
    ap 是一个 va_list 类型的变量,
    type 是下一个参数的类型。
    该宏返回类型为 type 的值,并将 ap 指向下一个参数。

  • va_end(ap):结束可变参数列表的访问。
    ap 是一个 va_list 类型的变量。
    该宏将 ap 置为 NULL。

  1. int func(int, ... ) {
  2. .
  3. .
  4. .
  5. }
  6. int main() {
  7. func(2, 2, 3);
  8. func(3, 2, 3, 4);
  9. }
复制代码
  1. #include <stdio.h>
  2. #include <stdarg.h>
  3. double average(int num,...)
  4. {
  5. va_list valist; //函数内部使用 va_list 类型的变量 va_list 来访问可变参数列表。
  6. double sum = 0.0;
  7. int i;
  8. /* 为 num 个参数初始化 valist */
  9. va_start(valist, num);//
  10. /* 访问所有赋给 valist 的参数 */
  11. for (i = 0; i < num; i++)
  12. {
  13. sum += va_arg(valist, int);// va_arg() 宏获取下一个整数参数
  14. }
  15. /* 清理为 valist 保留的内存 */
  16. va_end(valist); //va_end() 宏结束可变参数列表的访问。
  17. return sum/num;
  18. }
  19. int main()
  20. {
  21. printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
  22. printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
  23. }
复制代码

■ 三维数组的传递

■ 计算公式最大值最小值和中间值

  1. 最大值
  2. #define MAX(a, b) (((a) > (b) ) ? (a) : (b))
  3. 最小值
  4. #define MIN(a, b) (((a) < (b) ) ? (a) : (b))
  5. 中间值
  6. #define MID(a,b,c) a>b?(a>c?(b>c?b:c):(a)):(a>c?(a):(b>c?c:b))
复制代码

■ memcpy

  1. #define MIN(a, b) (((a) < (b)) ? (a) : (b))
  2. #define MAX(a, b) (((a) > (b)) ? (a) : (b))
  3. #define MEMCPY(a, b, c) memcpy((a),(b),(c))
复制代码

■ offsetof(TYPE, MEMBER) 计算结构体变量中元素的位置

  1. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
复制代码

■ 结构体数组初始化

  1. typedef enum
  2. {
  3. FILE_DIR = 0,
  4. FILE_VIDEO,
  5. FILE_MUSIC,
  6. FILE_IMAGE,
  7. FILE_TXT,
  8. FILE_OTHER,
  9. FILE_ALL,
  10. }file_type_t; //valid types arranged be alphabet
  11. typedef enum
  12. {
  13. MEDIA_TYPE_VIDEO, // 电影
  14. MEDIA_TYPE_MUSIC, // 音乐
  15. MEDIA_TYPE_PHOTO, // 照片
  16. MEDIA_TYPE_TXT, // 电子书
  17. MEDIA_TYPE_COUNT,
  18. } media_type_t;
  19. #define MEDIA_FILE_DEF(x,y) \
  20. { \
  21. .ext_name = x, \
  22. .ext_count = HC_ARRAY_SIZE(x), \
  23. .file_type = y, \
  24. }
  25. #define EXT_NAME_LEN 12
  26. typedef struct{
  27. char (*ext_name)[EXT_NAME_LEN];
  28. int ext_count;
  29. file_type_t file_type;
  30. }file_ext_t;
  31. static file_ext_t m_meida_ext[] = {
  32. [MEDIA_TYPE_VIDEO] = MEDIA_FILE_DEF(m_movie_support_ext, FILE_VIDEO),
  33. [MEDIA_TYPE_MUSIC] = MEDIA_FILE_DEF(m_music_support_ext, FILE_MUSIC),
  34. [MEDIA_TYPE_PHOTO] = MEDIA_FILE_DEF(m_photo_support_ext, FILE_IMAGE),
  35. [MEDIA_TYPE_TXT] = MEDIA_FILE_DEF(m_txt_support_ext, FILE_TXT),
  36. };
复制代码

■ str 函数

■ _strlen函数实现

  1. static int _strlen(const char* s) {
  2. int Len;
  3. Len = 0;
  4. if (s == NULL) {
  5. return 0;
  6. }
  7. do {
  8. if (*s == 0) {
  9. break;
  10. }
  11. Len++;
  12. s++;
  13. } while (1);
  14. return Len;
  15. }
复制代码

■ strstr函数使用

[1] 函数原型

char *strstr(const char *haystack, const char *needle);
[2] 头文件

#include
[3] 函数功能

搜索"子串"在"指定字符串"中第一次出现的位置
[4] 参数说明

haystack -->被查找的目标字符串"父串"
needle -->要查找的字符串对象"子串"
注:若needle为NULL, 则返回"父串"

[5] 返回值

(1) 成功找到,返回在"父串"中第一次出现的位置的 char *指针
(2) 若未找到,也即不存在这样的子串,返回: “NULL”

  1. #include <stdio.h>
  2. #include <string.h>
  3. int main(int argc, char *argv[])
  4. {
  5. char *res = strstr("xxxhost: www.baidu.com", "host");
  6. if(res == NULL) printf("res1 is NULL!\n");
  7. else printf("%s\n", res); // print:-->'host: www.baidu.com'
  8. res = strstr("xxxhost: www.baidu.com", "cookie");
  9. if(res == NULL) printf("res2 is NULL!\n");
  10. else printf("%s\n", res); // print:-->'res2 is NULL!'
  11. return 0;
  12. }
复制代码

■ strcasestr函数

[1] 描述

strcasestr函数的功能、使用方法与strstr基本一致。
[2] 区别

strcasestr函数在"子串"与"父串"进行比较的时候,“不区分大小写”
[3] 函数原型

#define _GNU_SOURCE
#include
char *strcasestr(const char *haystack, const char *needle);

  1. #define _GNU_SOURCE // 宏定义必须有,否则编译会有Warning警告信息
  2. #include <stdio.h>
  3. #include <string.h>
  4. int main(int argc, char *argv[])
  5. {
  6. char *res = strstr("xxxhost: www.baidu.com", "Host");
  7. if(res == NULL) printf("res1 is NULL!\n");
  8. else printf("%s\n", res); // print:-->'host: www.baidu.com'
  9. return 0;
  10. }
复制代码

■ char *strdup(const char *s);

strdup()函数主要是拷贝字符串s的一个副本,由函数返回值返回,这个副本有自己的内存空间,和s没有关联。
strdup 函数复制一个字符串,使用完后,要使用delete函数删除在函数中动态申请的内存,
strdup函数的参数不能为NULL,一旦为NULL,就会报段错误,因为该函数包括了strlen函数,而该函数参数不能是NULL。

注: strdup()函数是c语言中常用的一种字符串拷贝库函数,一般和free()函数成对出现。

  1. char* __strdup(const char* s)
  2. {
  3. size_t len = strlen(s) + 1;
  4. void* ret = malloc(len);
  5. if(ret==nullptr)
  6. {
  7. return nullptr;
  8. }
  9. return (char*)ret;
  10. }
复制代码

■ C库函数

■ ftell(FILE *stream)

C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置。

声明下面是 ftell() 函数的声明。
long int ftell(FILE *stream)
参数
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
返回值
该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。

■ printf

■ snprintf

  1. int snprintf(char *str, size_t size, const char *format, ...)
  2. snprintf() 是一个 C 语言标准库函数,用于格式化输出字符串,并将结果写入到指定的缓冲区,与 sprintf() 不同的是,
  3. snprintf() 会限制输出的字符数,避免缓冲区溢出。
  4. str -- 目标字符串,用于存储格式化后的字符串的字符数组的指针。
  5. size -- 字符数组的大小。
  6. format -- 格式化字符串。
  7. ... -- 可变参数,可变数量的参数根据 format 中的格式化指令进行格式化。。
复制代码

■ getopt() 函数

  1. C语言处理参数的 getopt() 函数
  2. while ((ch = getopt(argc, argv, "hHsSp:P:")) != EOF) {
  3. switch(ch) {
  4. case 'h' :
  5. case 'H' :
  6. usbd_msg_help_info();
  7. return 0;
  8. case 'p' :
  9. case 'P' :
  10. usb_port = atoi(optarg);
  11. udc_name = get_udc_name(usb_port);
  12. if(udc_name == NULL){
  13. printf("[error] parameter(-p {usb_port}) error,"
  14. "please check for help information(cmd: g_mass_storage -h)\n");
  15. return -1;
  16. }
  17. break;
  18. case 's' :
  19. case 'S' :
  20. hcusb_gadget_msg_deinit();
  21. hcusb_set_mode(usb_port, MUSB_HOST);
  22. is_deinit = true;
  23. break;
  24. default:
  25. break;
  26. }
  27. }
复制代码

■ bzero

  1. 在"string.h"头文件中
  2. 原型为:void bzero(void *s, int n); //能够将内存块(字符串)的前n个字节清零,
复制代码

■ sscanf 利用 sscanf和正则表达式提取数据

参考博主

%c 一个单一的字符
%d 一个十进制整数
%i 一个整数
%e, %f, %g 一个浮点数
%o 一个八进制数
%s 一个字符串
%x 一个十六进制数
%p 一个指针
%n 一个等于读取字符数量的整数
%u 一个无符号整数
%[ ] 一个字符集 (正则就使用这个,而且仅支持贪婪模式,能匹配个多少就匹配多少个)
%% 一个精度符

示例一

  1. #include <stdio.h>
  2. #define STR "AT+COM=aabbcc"
  3. void main(void)
  4. {
  5. int result = 0;
  6. char str_para1[50] = {0};
  7. result = sscanf(STR, "AT+COM=%s", str_para1);
  8. printf("result------> %d \r\n", result);
  9. printf("parameter1--> %s \r\n", str_para1);
  10. }
  11. 输出:
  12. result------> 1
  13. parameter1--> aabbcc
复制代码
  1. sscanf("AT+COM=aabbcc", "AT+COM=%3s", str_para1);
  2. printf("result------> %d \r\n", result);
  3. printf("parameter1--> %s \r\n", str_para1);
  4. 输出:
  5. result------> 1
  6. parameter1--> aab
复制代码

示例二


示例三

示例四

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Honkers

荣誉红客

关注
  • 4010
    主题
  • 36
    粉丝
  • 0
    关注
这家伙很懒,什么都没留下!

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行