嵌入式培训负债2w的第二周 下篇之指针_2

1. 再讲数组名作为函数传参

​ 数组作为函数的实参,本质上传的是数组的首地址

​ 当数组名作为函数的实参的时候,数组名当成是一个指针
​ 那形参该如何定义?

	(1)一维数组
	
	void Func(int x[], int n)
	{
		x[0] = 100
	}
	void Func_1(int*x, int n)
	{
		x[0] = 100
	}
	int main()
	{
		int a[4] = {1,2,3,4};
		Func(a,4);// 代表数组:typeof(a) ==> int[4]
		Func_1(a,4);// 看作是指针 :typeof(a) ==> int*
	}
		
	(2)二维数组
	
	void Func(int x[][4], int n)
	{}
	
	void Func_1(int(*x)[4], int n)
	{
		x[0][0] <===>a[0][0]
		
		//*x <==> x指向的对象 ==> a[0]   *(x+0) ==> x[0]
		
		//x ==> &a[0]
	}
	
	int main()
	{
		int a[3][4] = {0};
		Func(a, 3);//代表数组:typeof(a) ==> int[4][3] 
		Func_1(a, 3);//  看作是指针 :typeof(a) ==> int[4]*    Func_1(&a[0], 3)
	}

结论:

​ 数组名作为函数参数,本质上 传的是 首元素的地址

2.多级指针

  • 如果一个指针变量p1存储的地址,是另一个普通变量a的地址,那么称p1为一级指针

  • 如果一个指针变量p2存储的地址,是指针变量p1的地址,那么称p2为二级指针

  • 如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址,那么称 p3 为三级指针

  • 以此类推,p2、p3等指针被称为多级指针

  • 示例:

	int a;
//定义一个指针变量p,保存a的地址
	int*p = &a; // p: 一级指针
	
//定义一个指针变量p1, 来保存p的地址
	typeof(p) *p1 = &p;
==>  int* *p1 = &p; // p1 :二级指针
	
//定义一个指针变量p2, 来保存p1的地址
	typeof(p1) *p2 = &p1;
==> int** *p2 = &p1;// p2: 三级指针

//注意: 无需搞清楚到底是几级指针, 只要知道是一个指针,该指针指向的对象是什么类型

3.指针万能拆解法

  • 任意的指针,不管有多复杂,其定义都是由两部分组成
    - 第一部分:指针所指向的对象的数据类型,可以是任意类的类型
    - 第二部分:*指针的名字

在这里插入图片描述

  • 示例:
char   (*p1);      // 第2部分:*p1; 第1部分:char; 
char  *(*p2);      // 第2部分:*p2; 第1部分:char *; 
char **(*p3);      // 第2部分:*p3; 第1部分:char **; 
char   (*p4)[3];  // 第2部分:*p4; 第1部分:char [3]; 
char   (*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float); 
  • 注解:
    1. 上述示例中,p1、p2、p3、p4、p5本质上并无区别,它们均是指针
    2. 上述示例中,p1、p2、p3、p4、p5唯一的不同,是它们所指向的对象的数据类型不同
    3. 第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边

4.指针函数与函数指针

指针函数:本质上是一个函数,返回值类型是指针类型

int* Get_Memory_int()// ==>指针函数
{
	static int n;
	
	return &n;
}

int main()
{
	int*p = Get_Memory_int();
	int*q = Get_Memory_int();
}

函数指针:

  • 概念:指向函数的指针,称为函数指针。

  • 本身是一个指针,指向的对象是一个函数

  • 特点:函数指针跟普通指针本质上并无区别

  • 在C语言中 , 函数名代表函数的地址
    			&函数名  
    
    	(1)函数指针如何定义?
    			指针的定义:
    				指向的对象的类型 * 指针变量名
    				
    		函数 : 函数头 + 函数体
    		定义:		 函数返回值类型 函数名(函数形参列表)   ==》 函数头
    					{}   ==》 函数体
    					
    		函数类型: 函数返回值类型 (函数形参列表)
    			
    				 
    	eg:
        	int Func(int n)
        	{
        		printf("Func:%d\n", n);
        	}
    		// 定义一个指针变量p保存Func的地址
    			typeof(Func) *p;
    		==> int (int) *p;
    		==> int (*p)(int);// p:函数指针,指向一个带int型参数,返回值为int型的函数
    
        (2)如何给函数指针赋值?
        		p = Func;//  p(2) 
        		or
        		p = &Func;// (*p)(2)
    
    
        (3)怎么通过函数指针去调用指向的函数?
        		p(2)
        		or
        		(*p)(2)
    
        通过函数指针去调用所指向的函数:
        指针变量名(实参列表);
        (*指针变量名)(实参列表);
    
  • 示例:

练习:
	定义一个函数,通过指针变量p去间接调用
	
	#include <stdio.h>
    void Func(int n)
    {
        printf("Func:%d\n", n);
    }
	void YYY(int n)
    {
        printf("YYY:%d\n", n);
    }
	void XXX(int n)
    {
        if(n <= 0)
            Func(n);
        else
            YYY();
    }
    int main()
    {
        void(*p)(int);// p:函数指针,指向一个带int型参数,返回值为int型的函数
        //p = Func;
       // p = &Func;

        //p(2);
        //(*p)(3);

    }
  • 回调函数:一个函数作为参数进行传参,该函数被称为回调函数

  • 要点:

    1. 函数指针是一类专门用来指向某种类型函数的指针。

    2. 函数的类型不同,所需要的函数指针也不同。

      注意: 在给函数指针指向函数的时候 一定要对应 函数类型
      
    3. 函数的类型,与普通变量的类型判定一致,即去除声明语句中的标识符之后所剩的语句。

5.字符串处理函数

函数strstr

  • 注意:
    • 该函数仅仅只是查找是否有该子串,返回的是该子串所在的首字符地址
  • 示例:
#include <stdio.h>
#include <string.h>

int main()
{
    char*s = "HF240401_pxl_1";

    char*p = strstr(s,"pxl");
    if(p == NULL)
    {
        printf("未找到\n");
    }
    else
    {
        printf("p = %s\n", p);
    }
}
练习:
	写一个程序,实现将一个ip字符串中每个数字进行输出,ip字符串通过键盘输入
	eg:
		char *s = "192.168.10.165";
	==> 192
		168
		10
		165
#include <stdio.h>
#include <string.h>
int main()
{
    //char s[] = "192.168.10.165";
    //char *s;
    char s[128] = {0};
    //scanf("%s", s);
    gets(s);
    char*start = s;// 指向字符串起点位置

    while(1)
    {
        char*p = strstr(start, ".");
        if(p == NULL)
        {
            printf("%s\n", start);
            break;
        }    
        *p = '\0';
        printf("%s\n", start);
        start = p+1;
    }
   
   

}

函数strlen

字符串长度:第一个字符距离第一个’\0’的元素个数,不包含‘\0’

  • 示例:
char *s = "123\0a123456";
strlen(s) ==> 3
s = "123\0123456123";
sizeof(s) ==> 8
strlen(s) ==> 11
char s[20] = "123\17891234";
sizeof(s) ==> 20
strlen(s) ==>  10
    
char s[] = "123456";
strlen(s+2) ==> 4

函数strtok

  • 注意:
    1. 该函数会将改变原始字符串 str,使其所包含的所有分隔符变成结束标记 ‘\0’ 。
    2. 由于该函数需要更改字符串 str,因此 str 指向的内存必须是可写的。
    3. 首次调用时 str 指向原始字符串
      1. str == NULL 使用的是从之前子串后一个字符的位置
      2. 若自己给定str ,从str指定的位置开始
  • 示例:
#include <stdio.h>
#include <string.h>
int main()
{
    char s[] = "www.baidu.com";

    char *p = strtok(s, ".");
    printf("p = %s\n", p);
    p = strtok(NULL, ".");
    printf("p = %s\n", p);
    p = strtok(NULL, ".");
    printf("p = %s\n", p);
    p = strtok(NULL, ".");
    printf("p = %s\n", p);
}

函数strcat与strncat

  • 注意:
    1. 这两个函数的功能,都是将 src 中的字符串,复制拼接到 dest 的末尾。

    2. strcat() 没有边界控制,因此可能会由于 src 的过长而导致内存溢出:越界。

    3. strncat() 有边界控制,最多复制 n+1 个字符(其中最后一个是 ‘\0’ )到 dest 的末尾。

      ​ 在复制过程中,strncat结束有两种情况:1. 遇到\0但是还没有复制到n个字符

      ​ 2. 已经复制了n个字符没有遇到\0 , 最后会补一个\0

      ​ 以上两种情况最后都会有\0

  • 示例:
int main()
{
    char s1[32] = {"123456\0a78910"};
    char s2[] = {"12"};
    //char *p = strcat(s1+2,s2+3);

    //printf("%p %p %p\n", s1, s2, p);
    char*p = strncat(s1,s2,6);
    printf("p  = %s\n", p);
    for(int i = 0; i < 32; i++)
    {
        printf(".%c", s1[i]);
    }
    printf("\n");
}
  • 注意:strncat()是具备边界检查的安全版本,推荐使用。

函数strcpy与strncpy

NAME
strcpy, strncpy - copy a string

SYNOPSIS
#include <string.h>

   char *strcpy(char *dest, const char *src);
   		dest: 指向的空间用来存储要拷贝的字符串
   		src: 指向要拷贝字符串
   		返回值:
   			返回 :拷贝之后新的字符串首地址  等价于dest
   			
   		strcpy函数存在越界的风险
   char *strncpy(char *dest, const char *src, size_t n);
   		dest: 指向的空间用来存储要拷贝的字符串
   		src:  指向要拷贝字符串
   		n :   最多拷贝src指向的字符串前n个字符
   		返回值:
   			返回 :拷贝之后新的字符串首地址  等价于dest
  • 注意:

    1. 这两个函数的功能,都是将 src 中的字符串,复制到 dest 中。

    2. strcpy() 没有边界控制,因此可能会由于 src 的过长而导致内存溢出:越界。

    3. strncpy() 有边界控制,最多复制 n 个字符到 dest 中。

    4. 在复制过程中 ,

      ​ 1.复制的字符数 <=n 的时候,遇到\0结束, \0 也会复制过去

      ​ 2.如果 字符数 ==n 的时候期间一直未遇到\0, 拷贝完n个字符数结束,\0不会被拷贝

  • 注意:strncpy()是具备边界检查的安全版本,推荐使用。

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
        //char s1[32] = {"123"};//1 2 3 \0 1 2 3 4 5 6 \0 \0 \0
        // char s1[] = {"123"};
        // char* s2 = "123456";
    
        //strcpy(s1,s2);
    
        char s1[32] = {"12345\0asdfg"};
        char *s2 = "as123";
        strncpy(s1,s2, 2);
        printf("s1 = %s\n", s1);
    }
    

函数strcmp与strncmp

  • 注意:

    • 比较字符串大小,实际上比较的是字符的 ASCII码值的大小。

    • 从左到右逐个比较两个字符串的每一个字符,当能“决出胜负”时立刻停止比较。

    • strcmp和strncmp这两个函数:两个字符串不相同返回的是 两个字符的ASCII码的差值

      返回值: s1 > s2 ==> 结果 > 0

      ​ s1 == s2 ==> 0

      ​ s1 < s2 ==> < 0

  • 示例:

strcmp("123", "asd");//   ==>  <0

strncmp("zhangsan","zhang", 5);// 只比较字符串前5个字符

// 字符串 后4个字符 为 .txt 的字符串
char*s = "1.txt"; 
strcmp(s+strlen(s)-4, ".txt") == 0
    
int main()
{
    char *s1 = "1231";
    char *s2 = "12w1";
    

    printf("%d\n", strncmp(s1,s2,10));// -68
    printf("%d\n", strncmp(s1,s2,-1));// -68
    printf("%d\n", strcmp(s1,s2));//-68
}

函数strchr与strrchr

  • 注意:

    1. 这两个函数的功能,都是在指定的字符串 s 中,试图找到字符 c。
    2. strchr() 从左往右找,strrchr() 从右往左找。
    3. 字符串结束标记 ‘\0’ 被认为是字符串的一部分。
  • 示例:

    char *p = strchr("123415",'1');
    
    

6.void型指针

​ 通用指针,泛型指针

  • 概念:无法明确指针所指向的数据类型时,可以将指针定义为 void 型指针

  • 要点:

    1. void 型指针无法直接索引目标,必须将其转换为一种具体类型的指针方可索引目标
    2. void 型指针无法进行加减法运算
  • void关键字的三个作用:

    1. 修饰指针,表示指针指向一个类型未知的对象。
    2. 修饰函数参数列表,表示函数不接收任何参数。
    3. 修饰函数返回类型,表示函数不返回任何数据。
    注意:

    ​ 一般void* 指针都出现在 函数参数或者是函数返回值类型的时候

    ​ 如果出现在 函数参数的时候 表示实参可以是任意类型的指针

    ​ 如果出现在 函数返回值类型的时候 表示你可以用任意一个类型的指针来接收该函数的返回值

7.动态内存分布函数

​ 堆内存 :随进程(程序)的持续性

​ 用户在代码中告诉编译器你是否需要开辟空间,释放也由手动释放

malloc/realloc/calloc :用来动态开辟空间
					//在 堆内存中 开空间
free:释放 动态内存空间
		注意:释放 malloc/realloc/calloc 所开辟的空间
NAME
       malloc,  free,  calloc,  realloc  -  allocate  and free
       dynamic memory

SYNOPSIS
       #include <stdlib.h>
		malloc :用来在动态内存中分配size个字节的空间大小
       void *malloc(size_t size);
       		size: 要开辟的空间大小,以字节为单位  >0 
       	返回值:
       		成功返回开辟的空间首地址
       		失败返回NULL
       注意:
       	malloc开辟的空间不会自动初始化
       	如果要把该空间全部设置为某个字符或者是0
       		memset/bzero
  =======================================
       #include <string.h>
       void *memset(void *s, int c, size_t n);
	===========================
       #include <strings.h>
       void bzero(void *s, size_t n);
   ================================
       eg:
       		char *p = malloc(10);
			memset(p,0,10);/ bzero(p, 10);
     注意:
     		动态内存分配之后,不使用记得及时释放
     		若没有释放,又找不到该空间,该空间称为垃圾内存,该现象称为内存泄露
       	
       	==========================================
       free :释放 malloc/realloc/calloc 所开辟的动态空间
       		void free(void *ptr);
			ptr: 指向要释放的空间
	 =========================
       calloc :用来分配动态空间,分配nmemb个对象,每个对象size大小
       void *calloc(size_t nmemb, size_t size);
			nmemb:分配的对象个数
			size:每个对象所占空间大小,以字节为单位
		返回值:
       		成功返回开辟的空间首地址
       		失败返回NULL
      
	注意:
		calloc所分配的空间将会被初始化为0
	==================================	
        realloc :将ptr所指向的空间扩大的size大小(ptr要指向一块动态内存)
        	扩大:
        			原址扩充
        			异地扩充
        		记得重新接收该函数的返回值
       void *realloc(void *ptr, size_t size);
			ptr == NULL
					relloc(NULL,size) <==>malloc(size)
            ptr != NULL
            	size > 原来大小
            			扩充
				size == 原来大小
						不动
				size < 原来大小
					未知情况,未定义
				size == 0
                	free(ptr)
                
         返回值:
       		成功返回开辟的空间首地址
       		失败返回NULL
int main()
{
   
    int*p = malloc(100);
    
    int a;
    p = &a;// 此时 malloc的空间变成垃圾内存
   
}
int main()
{
   
    int*p = malloc(100);
    
    int a;
    free(p);
   // p = &a;
   p = NULL;
}

8.main函数的形式参数

​ 在Linux下面,程序运行时,main允许带参数,只不过所有的参数都当做是字符串来处理。

把所有的字符串传给main函数,所以在c程序中main里面可以带两个参数,第一个参数
int argc表示命令行参数的个数,第二个参数是一个字符串指针数组
里面每一个都是一个参数:字符串

int main(int argc,char*argv[])// int argc, char**argv
{
	//argv[0][0]
}
练习:使用从命令行输入的方式计算两个数的和
	
	./a.out 123 456
   ==> 123+456 = 379
#include <stdio.h>

int str_to_int(char*str)// str = NULL
{
    //printf
    if(str == NULL)
        return -1;
	if(*str < '0' || *str > '9')
    {
        return -1;
    }
    int num = 0;
    while(*str)
    {
        if(*str >= '0' && *str <= '9')
        {
            num = num*10 + *str - '0';
        }
        else
        {
            return num;
        }
           

        str++;
    }

    return num;

}
//./a.out 111 222
int main(int argc, char**argv)
{
    if(argc != 3)
    {
        printf("输入参数有误\n");
        return -1;
    }
    int num1 = str_to_int(argv[1]);
    int num2 = str_to_int(argv[2]);

    printf("%d + %d = %d\n",num1, num2, num1+num2);
    
}
  • 30
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值