目录
一级指针传参: (一级指针变量其实就是个地址,即整形整数,传参时用一级指针接收)
3.1.3 strcat (字符串追加/连接,在字符串后面追加上另一个字符串)
一. 数据的存储
1.0 运算符优先级
从左到右依次降低
1.1数据类型介绍
1.2 整形在内存中的存储
原码 | 反码 | 补码 | |
int a = 20; | 0001 0100 | 0001 0100 | 0001 0100 |
int b = -10; | 1000 1010 | 1111 0101 | 1111 0110 |
1.3 整形提升和算术转换
整形提升:当小于int的类型 (char、short) 在进行不同类型的计算时会发生整形提升。
C的整型算术运算总是至少以缺省整型类型的精度来进行的。当两个小于int整形的的操作数在计算机中进行计算时,计算机为了获得精度会先将两个类型在计算前先转换为int的整形。
整型提升是按照变量的数据类型(自身的数据类型)的符号位来提升的
1.3.1 正数的整形提升,高位补充符号位,即为0
char a = 126;
char b = 5;
char c = a + b;
分析:计算机在运算时使用的是补码进行计算,当操作数进行运算时,使用的是补码,正数的原码和补码相同,但计算出来的还是补码,补码在需要打印时会先转换成原码的形式在进行打印。因为char小于int的4个字节,所以在计算时会发生整形提升,将char由8bit提升为32bit,然后相加得到一个补码。再将补码转换成原码以整形(32bit)的形式打印出来。
char a : 0111 1110, 整形提升为 00000000 00000000 00000000 0111 1110
char b :0000 0101,整形提升为 00000000 00000000 00000000 0000 0101
相加后的补码为 00000000 00000000 00000000 1000 0011, 再赋值给char c,即c在内存中以补码形式存放的二进制为1000 0011。
若c以char的形式去读取: 反码=补码-1,1000 0010; 原码:1111 1101,即-125;
若c 为unsigned char的形式去读取,反码=补码=原码,1000 0011,即为131。
1.3.2 负数的整形提升,高位补充符号位,即为1
char a = -10;
char b = 20;
int c = a + b;
char a : 1000 1010, 整形提升为 11111111 11111111 11111111 1000 1010
char b : 0001 0100, 整形提升为 00000000 00000000 00000000 0001 0100
相加后的补码为 11111111 11111111 11111111 1001 1110, 再赋值给int c。即为c在内存中以补码形式存放的二进制。
c以 int 的形式去读取,反码=补码-1,11111111 11111111 11111111 1001 1101
原码:10000000 00000000 00000000 0110 0010,即-98。
1.3.3 算术转换:
当大于或等于int的不同类型要进行运算发生的就叫做算术转换:unsigned int、long、float、double、long long、long double
当一个int 与unsigned int进行计算时,会先将int 通过算术转换为unsigned int,再与unsigned int计算,如果是一个整形在与浮点型进行计算会将整形先转换成浮点型再进行计算。同类型的计算不会发生算术转换(不包括char和short)。
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
答案是输出是 ">6"。原因是当表达式中存在 有符号类型 和 无符号类型 时所有的操作数都 自动转换为无符号类型。因此-20 变成了一个非常大的正整数,所以该表达式计算出的结果大于 6。
1.3.4 sizeof里是不参与运算的
表达式都有一个值属性和一个类型属性。一个double类型和int类型的进行计算,计算后的结果就是值属性,两个操作数一个是int类型,一个是double类型,就是类型属性。而sizeof会根据表达式来判断是否存在整形提升或算术转换,然后只需要推断下最后得到的类型是什么,最后计算字节大小就可以了,不会进行值计算。
#include<stdio.h>
int main()
{
int a = 4;
double b = 4.0f;
printf("%d\n",sizeof(a+b)); //8,但a+b不会进行运算
return 0;
}
1.4 大小端判断
小端模式:数据的低位放在低地址空间 (起始地址),数据的高位放在高地址空间
大端模式:数据的高位放在低地址空间 (起始地址),数据的低位放在高地址空间
数据读取时,是由 低地址-->高地址 顺序读取。如32位int型数0x12345678的存储:
//方法一
void check_sys() //内存中数据的存储:由低地址到高地址
{
int num = 1; //小端: 00 00 00 01 大端: 01 00 00 00
char *p = (char *)#
if( *p == 1 ) //读取低地址的字节
printf("小端存储");
}
//方法二
union UN
{
char c; //数据都是从低地址开始读起
int i; //数据都是从低地址开始存放的
};
int main()
{
union UN a;
a.i = 1;
if( a.c == 1 ) //读取c,若对应i的低字节,即低地址--低字节,为小端
printf("小端存储");
}
1.5 浮点数的存储规则
(-1)^S * M * 2^E(-1)^s 表示符号位,当 s=0 , V 为正数;当 s=1 , V 为负数。M 表示有效数字,大于等于 1 ,小于 2 。2^E 表示指数位。
二. 指针的进阶
* 号的优先级
a. 优先级低于数组,如数组指针应该写 int (*par) [10]
b. 优先级低于取结构体成员,如Struct结构体成员名name,p指向结构体,*p.name中,p优先
与.name结合,再与*结合
c. 优先级低于( ),如函数指针int (*p) (int, int),*跟p须( )起来,否则优先跟后面的(int, int )结合
2.1 字符指针
指向字符的指针
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
int main()
{
const char* pstr = "hello bit."; //pstr指针变量指向了常量字符串
printf("%s\n", pstr);
return 0;
}
2.2 指针数组
是一个数组,数组的里放的是指针
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5]; //二级字符指针的数组
数组名表示的是数组首元素的地址,除了两种例外:
sizeof (数组名) 。表示整个数组的大小,而不是首元素的大小;
&数组名。取出的是数组全部元素的地址,指针若+ - 整数时,将会移动整个数组大小的位移。
2.3 数组指针
是一个指针,指针指向一个数组。
注意:[ ]的优先级要高于*号的,所以必须加上()来保证 p 和 * 先结合。
int *p1[10]; //指针数组
int (*p2)[10]; //数组指针
2.3.1 数组指针的使用:二维数组传参
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col) //传统方式
{
int i = 0;
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int (*arr)[5], int row, int col) //数组指针方式
{
int i = 0;
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
练习:下列是什么
int arr[5]; //整形数组
int *parr1[10]; //指针数组,其指针指向一个整形
int (*parr2)[10]; //数组指针,指向整形数组的指针
int (*parr3[10])[5]; //数组,数组存放的变量类型是数组指针
------------------------------------------------------------------
parr1先跟[10]结合,是个数组,数组里存放的是指针int *
parr2先跟*结合,是个指针,指针指向的是数组int [10]
parr3先跟[10]结合,是个数组。数组的类型是int (* )[5],即指向整形数组的指针
技巧:parr3[10]已经确定是个数组了,把数组拿掉,剩下的int (* ) [5]就是数组的类型了。
2.4 数组传参、指针参数
一维数组传参:
#include <stdio.h>
void test(int arr[]) //形参为数组
{}
void test(int arr[10]) //形参为数组,10可不写,因为本质传的是地址不是数组
{}
void test(int *arr) //形参为指针
{}
void test2(int **arr) //*arr来接收数组,数组的类型是int *
{}
int main()
{
int arr[10] = {0}; //数组
int *arr2[20] = {0}; //指针数组
test(arr);
test2(arr2);
}
二维数组传参;
void test(int arr[3][5]) //形参为数组
{}
void test(int arr[][5]) //二维数组的行可省略不写,列不可省略
{}
void test6(int(*arr)[5]) //形参为指针。用指针接收,其指针指向一个一维数组
{} ^
|
int main() |
{ |
int arr[3][5] = {0}; v
test(arr); //数组名为首元素地址,即一个一维数组
}
一级指针传参: (一级指针变量其实就是个地址,即整形整数,传参时用一级指针接收)
#include <stdio.h>
void print(int *p, int sz) //一级指针变量接收,变量类型是int *
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
print(p, sz); //一级指针变量p作为参数,传给函数
return 0;
}
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test(int* p) //一级指针接收
{ }
int main()
{
int a = 0;
test(&a); //方法1:一个数
int a = 0;
int* ptr = &a;
test(ptr); //方法2:一级指针 (值传递)
int arr[10] = { 0 };
test(arr); //方法3:一维数组
}
二级指针传参:
#include <stdio.h>
void test(int** ptr)
{
printf("num=%d \n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p); //一级指针的地址
return 0;
}
当一个函数的参数部分为二级指针的时候,函数能接收什么参数?
void test(int** p)
{ }
int main()
{
int** ptr;
test(ptr); //方法1:二级指针
int* p2;
test(&p2); //方法2:一级指针 (地址传递)
int* arr[10];
test(arr); //方法3:指针数组
}
2.5 函数指针
函数指针解引用: c = ( *pf2 ) ( 1,2 ) // pf2为函数指针
- 函数名可直接表示函数的地址,类似于数组名。
- * 号优先级低于( )。函数指针 int (*p) (int, int), *p要( )起来,否则p优先跟(int,int )结合
- c = ( *pf2 ) ( 1,2 ) 可以改成 c = pf2 ( 1,2 ), *号可省略,很重要
函数指针的形式: 函数的返回值类型(*指针名)(函数的参数列表类型)
int (*ptf)(int,int) // 这样是定义一个函数指针 变量
typedef int ( *pFUN)(int,int) // 这样是定义了一个函数指针 类型
int Add(int x, int y) {
return x+y;
}
int main()
{
int (*pf)(int, int) = &Add //直接定义函数指针pf
pFUN pf2; //通过指针类型定义变量
int c = (*pf2)(1,2); //函数指针的应用
}
函数作为参数传参时,参数类型为 指向该函数类型的指针
Typedef int (*pFUN) (int, int); //定义函数指针
int fun( int x, int y ) //函数fun
{
return x+y;
}
int function( pFUN fun, int a, int b) //函数fun作为参数传参的类型,为函数指针
{
return fun(a,b);
}
2.6 函数指针数组
是一个数组,数组里存放函数指针,即存放函数的地址。
变量定义举例:首先是个数组 arr[10],数组存放的类型是函数指针 (void) (* ) ( int, int ), 两个加起来就是函数指针数组:(void) (* arr [10] ) ( int, int ) = { 0 };
#include <stdio.h>
int add(int a, int b){
return a + b;}
int sub(int a, int b){
return a - b;}
int mul(int a, int b){
return a * b;}
int div(int a, int b){
return a / b;}
int main()
{
int x, y;
int input = 1;
int ret = 0; //函数名表示函数地址
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y); //*p解引用,得到对应的函数,再传参x,y
}
else
printf( "输入有误\n" );
printf( "ret = %d\n", ret);
}
return 0;
}
2.7 指向函数指针数组的指针
是一个指针,指针指向函数指针数组。
变量定义举例:首先是个指针:*p,指针指向数组:(*p) [10] ,数组存放的类型是函数指针
(void) (* ) ( int, int );加起来就是:(void) (* (*p) [10] ) ( int, int ) 。
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
void (*pfun)(const char*) = test; //函数指针pfun
void (*pfunArr[5])(const char* str); //存放函数指针的数组pfunArr
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
2.8 回调函数(call-back)
2.8.1 回调函数介绍
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
讲人话就是
把一个函数Call-Back(地址)作为参数传递传给其他函数P,函数P的形参用函数指针接收。而这个函数Call-Back会在某个时刻被调用执行,函数Call-Back就叫
回调函数
。如果代码立即被执行就称为
同步回调
,如果过后再执行,则称之为异步回调
。
举例:假设我们要使用一个排序函数来对数组进行排序,那么在主程序(Main program)中,我们先通过库,选择一个库排序函数(Library function)。但排序算法有很多,有冒泡排序,选择排序,快速排序,归并排序。同时,我们也可能需要对特殊的对象进行排序,比如特定的结构体等。库函数会根据我们的需要选择一种排序算法,然后调用实现该算法的函数来完成排序工作。这个被调用的排序函数就是回调函数(Callback function)。
int Callback_1(int a) ///< 回调函数1
{
printf("Hello, this is Callback_1: a = %d ", a);
return 0;
}
int Callback_2(int b) ///< 回调函数2
{
printf("Hello, this is Callback_2: b = %d ", b);
return 0;
}
int Callback_3(int c) ///< 回调函数3
{
printf("Hello, this is Callback_3: c = %d ", c);
return 0;
}
int Handle(int x, int (*Callback)(int)) ///< 这里形参用函数指针接收函数
{
(*Callback)(x);
}
int main()
{
Handle(4, Callback_1);
Handle(5, Callback_2);
Handle(6, Callback_3);
return 0;
}
2.8.2 回调函数的应用
#include <stdio.h>
#include <stdlib.h>
/**************** 函数指针结构体 **************/
typedef struct _OP {
float (*p_add)(float, float);
float (*p_sub)(float, float);
float (*p_mul)(float, float);
float (*p_div)(float, float);
} OP;
/***************** 加减乘除函数 ****************/
float ADD(float a, float b)
{ return a + b; }
float SUB(float a, float b)
{ return a - b; }
float MUL(float a, float b)
{ return a * b; }
float DIV(float a, float b)
{ return a / b; }
/**************** 初始化函数指针 **************/
void init_op(OP *op )
{
op->p_add = ADD;
op->p_sub = SUB;
op->p_mul = &MUL; //可加可不加&,函数名本身就是函数地址
op->p_div = &DIV;
}
/**************** 库函数 *******************/ //回调函数的接收:函数指针
float add_sub_mul_div(float a, float b, float (*op_func)(float, float))
{
return (*op_func)(a, b);
}
int main(int argc, char *argv[])
{
OP *op = (OP *)malloc(sizeof(OP));
init_op(op);
/* 直接使用函数指针调用函数 */
printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n",
(op->p_add)(1.3, 2.2), (*op->p_sub)(1.3, 2.2),
(op->p_mul)(1.3, 2.2), (*op->p_div)(1.3, 2.2));
/* 调用回调函数 */
printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n",
add_sub_mul_div(1.3, 2.2, ADD),
add_sub_mul_div(1.3, 2.2, SUB),
add_sub_mul_div(1.3, 2.2, MUL),
add_sub_mul_div(1.3, 2.2, DIV)); //回调函数的传参:函数地址
return 0;
}
2.8.3 一个实用的框架
一个GPRS模块联网的小项目,使用过的同学大概知道2G、4G、NB等模块要想实现无线联网功能都需要经历模块上电初始化、注册网络、查询网络信息质量、连接服务器等步骤,这里的的例子就是,利用一个状态机函数(根据不同状态依次调用不同实现方法的函数),通过回调函数的方式依次调用不同的函数,实现模块联网功能,如下:
/********* 工作状态处理 *********/
typedef struct
{
uint8_t mStatus;
uint8_t (* Funtion)(void); //函数指针的形式
} M26_WorkStatus_TypeDef; //M26的工作状态集合调用函数
/**********************************************
** >M26工作状态集合函数
***********************************************/
M26_WorkStatus_TypeDef M26_WorkStatus_Tab[] =
{
{GPRS_NETWORK_CLOSE, M26_PWRKEY_Off }, //模块关机
{GPRS_NETWORK_OPEN, M26_PWRKEY_On }, //模块开机
{GPRS_NETWORK_Start, M26_Work_Init }, //管脚初始化
{GPRS_NETWORK_CONF, M26_NET_Config }, /AT指令配置
{GPRS_NETWORK_LINK_CTC, M26_LINK_CTC }, //连接调度中心
{GPRS_NETWORK_WAIT_CTC, M26_WAIT_CTC }, //等待调度中心回复
{GPRS_NETWORK_LINK_FEM, M26_LINK_FEM }, //连接前置机
{GPRS_NETWORK_WAIT_FEM, M26_WAIT_FEM }, //等待前置机回复
{GPRS_NETWORK_COMM, M26_COMM }, //正常工作
{GPRS_NETWORK_WAIT_Sig, M26_WAIT_Sig }, //等待信号回复
{GPRS_NETWORK_GetSignal, M26_GetSignal }, //获取信号值
{GPRS_NETWORK_RESTART, M26_RESET }, //模块重启
}
/**********************************************
** >M26模块工作状态机,依次调用里面的12个函数
***********************************************/
uint8_t M26_WorkStatus_Call(uint8_t Start)
{
uint8_t i = 0;
for(i = 0; i < 12; i++)
{
if(Start == M26_WorkStatus_Tab[i].mStatus)
{
return M26_WorkStatus_Tab[i].Funtion();
}
}
return 0;
}
2.9 指针与 const 的关系
2.9.1 const 在 * 后面
int *const p1:表示常量指针,必须在定义时初始化,而且初始化后不能改变指向,但可以通过该指针修改内存里面的数据
1:必须在定义时初始化 (下列没初始化导致报错)
2:不能改变指向
3:可以通过该指针修改内存里面的数据
2.9.2 const 在 * 前面
int const *p2 、 const int *p3:表示指向常量的指针,不是必须初始化,能改变指向,但是不能通过该指针修改内存里面的数据。可以通过原来的变量修改内存的数据。
1.不是必须初始化
2.能改变指向,但不能修改内存所指向的内存里面的数据
3.可以通过原来的变量修改内存的数据
2.9.3 const int* const p4
第一种情况和第二种情况的结合。
2.9.4 函数参数加 const
三. 字符串函数 + 内存函数
函数介绍 | 求字符串长度 | strlen |
长度不受限制的字符串函数 | strcpy | |
strcat | ||
strcmp | ||
长度受限制的字符串函数介绍 | strncpy | |
strncat | ||
strncmp | ||
字符串查找 | strstr | |
strtok | ||
错误信息报告 | strerror | |
字符操作 | ||
内存操作函数 | memcpy | |
memmove | ||
memset | ||
memcmp |
3.1 字符串函数
3.1.1 strlen (计算字符串字节大小)
size_t strlen ( const char * str ); // Return the length of string
- 字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包 含 '\0' )
- 参数指向的字符串必须要以 '\0' 结束
- 注意函数的返回值为size_t,是无符号的( 易错 )
3.1.2 strcpy (字符串拷贝)
char* strcpy ( char * destination , const char * source ); // Return destination
- 源字符串必须以 '\0' 结束 (src 和 dest 所指内存区域不可以重叠)
- 会将源字符串中的 '\0' 拷贝到目标空间
- 目标空间必须足够大,以确保能存放源字符串
- 目标空间必须可变
3.1.3 strcat (字符串追加/连接,在字符串后面追加上另一个字符串)
char * strcat ( char * destination, const char * source );
- 源字符串必须以 '\0' 结束,会将源字符串中的 '\0' 拷贝到目标空间
- 目标空间必须有足够的大,能容纳下源字符串的内容
- 目标空间必须可修改
- 字符串不可自己给自己追加,'\0'会被覆盖掉,陷入死循环
3.1.4 strcmp (字符串比较)
int strcmp ( const char * str1 , const char * str2 ); //Return '>0' or '0' or '<0'
-
比较到 出现字符不一样 或者一个字符串结束 或者字符串全部比较完
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
#include<stdio.h>
#include<string.h>
int main()
{
char* p1 = "abcdef";
char* p2 = "abcdef";
char* p3 = "abcd";
char* p4 = "bcde";
printf("%d\n", strcmp(p1,p2 )); //0
printf("%d\n", strcmp(p1,p3 )); //1, P3提前遇到"\0",且e > "\0"
printf("%d\n", strcmp(p3,p4 )); //-1,a < b
}
3.1.5 strncpy
char * strncpy ( char * destination , const char * source , size_t num ); //Return destination
- 拷贝num个字符从源字符串到目标空间
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个
3.1.6 strncat
char * strncpy ( char * destination , const char * source , size_t num ); //Return destination
- 在字符串后面追加上另一个字符串的前n个字符
3.1.7 strncmp
int strncmp ( const char * str1 , const char * str2 , size_t num );
- 比较字符串的前n个字符
3.1.8 strstr (字符串中查找某段字符串)
char * strstr ( const char * str1 , const char * str2 );
- 在字符串str1中查找是否含有字符串str2
3.1.9 strtok (字符串分割)
char * strtok ( char * str , const char * sep );
- sep参数是个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记
- strtok函数找到 str 中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。 ( 注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷 贝的内容 并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
/* 字符串分割 */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] ="- This, a sample string.";
char * pch;
printf ("Splitting string \"%s\" into tokens:\n",str);
pch = strtok (str," ,.-");
while (pch != NULL)
{
printf ("%s\n",pch);
pch = strtok (NULL, " ,.-");
}
return 0;
}
/* 数组分割 */
#include <stdio.h>
int main()
{
char *p = "zhangpengwei@bitedu.tech";
const char* sep = ".@";
char arr[30];
char *str = NULL;
strcpy(arr, p);//将数据拷贝一份,处理arr数组的内容
for(str=strtok(arr, sep); str != NULL; str=strtok(NULL, sep))
{
printf("%s\n", str);
}
}
3.1.10 strerror
char * strerror ( int errnum );
- 获取指向错误消息字符串的指针
- 返回值为char * 类型 。指向描述错误错误的错误字符串的指针
函数解释:C语言的库函数在执行失败时,都会有一个错误码(0 1 2 3 4 5 6 7 8 9 ........),strerror会把错误码转成错误信息,其中errno会将错误码的错误信息的首字符地址返回来。
用法示例
字符分类函数:(如果不是要判断的字符,返回0,是的话返回非0)
函数 如果它的参数符合下列条件就返回真
iscntrl 任何控制字符
isspace 空白字符:空格‘ ’,换页‘\f’,换行'\n',回车'\r',制表符'\t',或垂直制表符'\v'
isdigit 十进制数字0~9
isxdigit 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
islower 小写字母a~z
isupper 大写字母A~Z
isalpha 字母a~z或A~Z
isalnum 字母或数字a~z,A~Z或0~9
ispunct 标点符号,任何不属于数字或字母的图像字符(可打印符号)
isgraph 任何图像字符
字符转换:
int tolower ( int c ); //大写字符转小写字符
int toupper ( int c ); //小写字符转大写字符
3.2 内存函数
需包含头文件include<string.h>
3.2.1 memcpy
void * memcpy ( void * destination , const void * source , size_t num );
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置
- 这个函数在遇到 '\0' 的时候并不会停下来
- 如果source和destination有任何的重叠,复制的结果都是未定义的
3.2.2 memmove
void * memmove ( void * destination , const void * source , size_t num );
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理
3.2.3 memcmp
int memcmp ( const void * ptr1 , const void * ptr2 , size_t num );
- 比较从ptr1和ptr2指针开始的num个字节
3.2.4 memset
是一个初始化函数,作用是逐字节地刷内存,将某一块内存中的全部字节设置为指定的值 0或 -1
void * memset ( void * s, int c, size_t n);
- s指向要填充的内存块
- c是要被设置的值,不能用它将int数组出初始化为0和-1之外的其他值
- n是要被设置该值的字节数
- 返回类型是一个指向存储区s的指针
四. 自定义类型
4.1 结构体
4.2 枚举
- 第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1
- 没有指定值的枚举元素,其值为前一元素加1
typedef enum
{
Yield_Mode, ///0x00,第一个元素不赋值,默认为0
Write_Sequence, ///0x01
Read_Sequence,
Read_Temperature = 0x10, ///0x10,直接赋值
Read_Humidity, ///0x11,上一个元素值+1
Read_Flash,
Network_Button_On,
Network_Button_Off,
Shut_Dowm ///0x15
}SMK_Command_t;
SMK_Command_t SMK_Command = Yield_Mode; //定义变量
void Test_FUN( SMK_Command_t mode ) //枚举作为参数
{
if( mode == Yield_Mode) xxx;
}
Test_FUN( &SMK_Command );
枚举定义举例
enum Color//颜色
{
RED=1,
GREEN=4,
BLUE //这里BLUE的值是GREEN+1,即5
};
typedef enum weekday//星期
{
Mon,
Tues,
Wed,
}weekday_t; //定义类型enum weekday
int main()
{
enum Color clr = GREEN; //只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
weekday_t weekday = Mon; //根据类型定义变量weekday
}
typedef enum //定义一些命令,命令是什么意思不重要↓
{
READ_ID = 0xEFC8, // command: read ID register
SOFT_RESET = 0x805D, // soft reset
SLEEP = 0xB098, // sleep
WAKEUP = 0x3517, // wakeup
}etCommands;
static result_t SHTC3_WriteCmd(etCommands cmd) //可以限定形参的类型
{
//
}
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
4.3 联合体
union UN
{
char c;
int i;
};
int main()
{
union UN a;
a.i = 1;
if( a.c == 1 )
printf("小端存储");
}
联合体大小:
1.联合体的大小至少是最大成员的大小
2.联合体的大小,不一定总是最大成员的大小
3.当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
#include <stdio.h>
union un
{
char arr[5]; //5个字节
int i; //4个字节
};
int main()
{
printf("%d\n", sizeof(union un)); //为8字节
return 0;
}
五. 动态内存的管理
为什么存在动态内存分配
我们已经掌握的内存开辟方式有:
int val = 20; //在栈空间上开辟四个字节
char arr[10] = {0}; //在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点:
5.1 malloc 和 free
void* malloc ( size_t size ); //向内存申请一块 连续可用 的空间,并返回指向这块空间的指针头文件:#include <malloc.h> 或 #include <stdlib.h>。 size单位是字节
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
void free ( void* ptr ); // 用来做动态内存的释放和回收,ptr是指向动态开辟内存的指针
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
#include <stdio.h> //需包含这个头文件
int main()
{
//代码1
int num = 0;
scanf("%d", &num);
int arr[num] = {0};
//代码2
int* ptr = NULL;
ptr = (int*)malloc(num*sizeof(int));
if(NULL != ptr) //判断ptr指针是否为空
{
int i = 0;
for(i=0; i<num; i++)
{
*(ptr+i) = 0;
}
}
free(ptr); //释放ptr所指向的动态内存
ptr = NULL; //是否有必要?
return 0;
}
5.2 calloc
void* calloc ( size_t num , size_t size );头文件:#include <malloc.h> 或 #include <stdlib.h> num为元素个数, size为元素大小
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p = (int*)calloc(10, sizeof(int));
if(NULL != p)
{
//使用空间
}
free(p);
p = NULL;
return 0;
}
5.3 realloc
头文件:#include <malloc.h> 或 #include <stdlib.h>
- realloc函数的出现让动态内存管理更加灵活
- 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。
void* realloc ( void* ptr , size_t size );头文件:#include <malloc.h> 或 #include <stdlib.h> size为要重新开辟的总字节数
- ptr 是将要改变的空间的起始地址
- size 调整后的大小,单位byte
- 返回值为调整后的内存的起始位置。
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
- realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
#include <stdio.h>
int main()
{
int *ptr = (int*)malloc(100); //先开辟一块空间使用
if(ptr != NULL)
{
//业务处理
}
else
{
exit(EXIT_FAILURE);
}
//扩展容量
//代码1
ptr = (int*)realloc(ptr, 1000); //这样不行。如果(空间不够)扩容失败会返回空指针&&
//而直接将空指针赋给ptr,ptr会丢失之前开辟的那块空间地址
//代码2
int*p = NULL;
p = realloc(ptr, 1000);
if(p != NULL)
{
ptr = p; //确保空间已经开辟好了再将ptr指向扩展的这块空间
}
//业务处理
free(ptr);
return 0;
}
5.4 常见的动态内存错误
- 对NULL指针的解引用操作
- 对动态开辟空间的越界访问
- 对非动态开辟内存使用free释放
- 使用free释放一块动态开辟内存的一部分
- 对同一块动态内存多次释放
- 动态开辟内存忘记释放(内存泄漏)
5.5 柔性数组
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
//代码1
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
p->a[i] = i;
}
free(p);
优势:
- 方便内存释放
- 这样有利于访问速度
六. 文件操作
七. 程序的编译
八. 补充
8.1 字符串数组、传参
传送门:C语言零碎知识点之字符串数组