C语言指针用法完善篇

 一,指针定义:

1,讲解

指针变量用来记录地址数据,没有记录有效地址的指针变量不可以使用。

 定义一个变量A和一个指针B,此时变量A存放在内存1000区间,将变量A赋值给指针变量B,此时指针变量B所接收到的并不是A的数值,而是A的内存区间地址1000。

int  data=200;
int *pp;
pp=&data;
printf("*pp=%d\n",*pp);
//------------------------
int num;
num=pp;
printf("num=%d\n",num);

指针变量可以用来表示捆绑的存储区。

指针也分类型,不同类型的指针适合记录不同类型存储区的地址

可以在一条语句里声明多个同类型的指针变量,每个指针变量名称前都应该加*号。

2,无效指针可以分成以下两种

(1)空指针里记录空地址(NULL),这个地址的数值就是数字0

(2)除了空指针以外的无效指针都叫做野指针

程序中禁止出现野指针,指针变量必须初始化。指针变量初始化的时候*没有参与赋值过。

3,指针定义

指针变量的取值范围取值0~4G,是一种数据类型(无符号整数,代表了内存编号)。它可以用来定义变量(与int、long一样),与int、long不同的它存储整数代表了内存的编号,通过这个变量可以访问对应编号的内存。

定义指针在变量名前名加上*即可:int a=10; int *b=a;

4,指针注意事项:空指针
变量指针的值等于NULL,这种指针叫空指针。不能对空指针解引用,一定会出现段错误。当操作重启时会跳转NULL地址,进行重启,因此NULL存储操作系统用于重启的数据。NULL在C语言中是一种错误标志,如果函数的返回值是指针类型,结果一旦NULL表示函数执行出错或失败。
 (1)如何避免空指针造成的段错误?

使用来历不明(函数的参数)的指针前先进行检查,if(NULL == p)。

 (2)野指针:指针变量的值不确定,使用野指针不一定会出错。
int* p; // 野指针
使用野指针的后果:段错误。注意:野指针是无法分辨的,所以比空指针危害更。
如何避免野指针造成的错误?
所有的野指针都人制造出来的,只要人人都不制造野指针就会有野指针造成的错误。

定义指针变量时一定要初始化。不返回局部变量的地址。

指针变量所指向的内存初始释放后要及时赋值为空(堆内存)。

4,指针的作用

(1)、堆内存无法取名字(无法使用标识符与堆内存建立联系),必须配合指针。

(2)、函数之间的参数是值传递(内存拷贝),使用指针可以优化参数的传递效率(需要对变量进行保护)。因为C语言采用的是值传递(内存拷贝),会随着变量字节数的增加而降低运行效率而传递变量的地址永远只拷贝4或8字节。

void func(const int* p);但使用指针变量的值可能会被修改,可以配合const。  

(3)、函数之间是相互独立的,有时协同配合需要共享变量(全局变量过多会造成命名冲突,不会被释放浪费内存),函数之间传递变量的地址可以达到共享变量的效果。

5,指针的好处:

(1)指针可以直接访问内存地址,可以提高效率

    在讲解数组的时候说过,访问数组元素有两种访问形式,一种是下标法,一种是指针法。下标法其实是对指针法的一种封装。当使用下标法访问数组元素的时候,在程序运行的时候最后还是要转换成指针进行访问。所以直接使用指针访问效率会更高。

 (2)在C语言中一些复杂的数据结构可以通过指针来实现。在C语言中的一些复杂结构,比如链表、树二叉树、红黑树等数据结构都是用指针进行构建。

 (3)在C语言中函数传参是值传递的,函数的形参是不可以修改变量值,但是通过指针却可以。 函数传参是不可修改变量的值,但是指针就可以。

#include <stdio.h>  //运行结果(p)[0] =2
int main(void)
{
	int i = 0,j = 0;
	int array[3][2] = // 定义一个二维数组
	{
		{0,1},
		{2,3},
		{4,5},
	};
	int (*p)[2] = array; // 定义一个数组指针并指针二维数组的首地址
	p++;                 // 数组指针加1,表示指指针向下移动一行。
	printf("(p)[0] = %d\r\n",*(p)[0]);
	return 0;
}

二,指针的用法与举例:

1,指针定义:类型* 变量名_p;  

#include <stdio.h>
int main(void)
{
	int *a;       // 定义整形指针变量
	int b = 10;   // 定义整形变量b	
	a = &b;       // 将b变量的地址赋值给指针a
	printf("*a = %d\r\n",*a);    // 打印出指针变量a的值
	printf("a  = 0x%x\r\n",a);  // 打印出指针变量a的地址
	printf("&b = 0x%x\r\n",&b); // 打印出变量b的地址
	return 0;
}

(1)指针变量与普通变量一样默认值不确定,一般初始化为NULL。

未初始化与非法指针:指针在定义时必须初始化,否则就会成为野指针

(2)指针变量的用法与普通变量不同,一般以p结尾加以区分。

 (3)指针变量的类型决定了通过指针变量访问内存时访问几个字节。

(4)指针变量不能连续定义(一个*只能定义出一个指针变量):

          int* p1,p2; // p是指针,p2是int类型变量

          int *p1,*p2; // p1和p2都是指针变量

赋值:指针变量 = 内存编号。   变量名_p = 地址;

    内存编号要能够访问,如果编号错误可能会造成段错误。void*可以与任意类型指针进行自动转换(C++中不行)。要保障地址与物理内存有对应关系(映射过),否则有段错误。

 int* p = malloc(4);

 int* p = # // 引用内存

访问:*指针变量 //解引用----访问指针变量

 根据指针变量中存储的内存编号去访问对应的内存。如果之前赋值的内存编号有误,这个步骤会出现段错误。访问的字节由指针类型决定。     int* p = #                   *p <=> num;

#include<stdio.h>
#include<stdlib.h>
int main()
{
	   int *p=(int *)malloc(sizeof(int)*5);
	   //因为malloc函数的返回值为void*,所以需要强制类型转换为对应类型。
		if (p == NULL)
		{
			printf("内存开辟失败\n");
		}
		else
		{
			printf("内存开辟成功\n");
			//使用指针
			for(int i=0;i<5;i++)
			{
				*(p+i)=i+1;
				printf("*(p+%d)=%d\n",i,*(p+i));
			}
			//使用结束,释放内存(后面介绍)
			free(p);
			p = NULL;
		}
}

2,指针运算

指针变量中存储的就是整数,因此为整型数据能使用的运算符指针变量基本都能使用,但不是所有运算都有意义。
指针+整数 = 指针+宽度*整数
指针-整数 = 指针-宽度*整数 // 指针进行前后移动
指针-指针 = (指针-指针)/宽度 // 两个指针之间相隔多少个元素
指针 >、<、<=、>= 指针 可以判断两个谁在前谁在后。

#include<stdio.h>
int main()
{
   int brr[8]={8,7,6,5,4,3,2,1};
   int *p_num=brr;
   for(int i=0;i<8;i++)
   {
       printf("*(p_num+%d)=%d\n",i,*(p_num+i));
   } 
   double data=3.567;
   double *p_data=&data;
   printf("*p_data=%g\n",*p_data);
   
   //---------------------------------

    char *p[5]={"China", "Russia", "England", "France", "America"};
   for(int k=0;k<5;k++)
   {
    printf("*(p+%d)=%s\n",k,*(p+k));
    printf("%s\n",p[k]);//p[k]  <----->  arr[k]  
    printf("%p\n",&p[k]); 
   }
}

指针的运算:只允许两种方式,一种是指针自加或自减,一种是指针减指针。

#include <stdio.h>
int main(void)
{
	int array[] = {0,1,2,3,4};
	int *a = &array[3];
	
	printf("a* = %d\r\n",*a);
	a++;
	printf("a* = %d\r\n",*a);
	a--;
	printf("a* = %d\r\n",*a);
	return 0;
}

 

3,指针和数组名称的区别

 .数组名就是个特殊的地址,也能当指针使用,数组名是个常量(数组名与数组第一个元素的首地址是对应关系,普通指针是指向关系)。数组名可以使用指针的解引用,而指针变量也可以使用数组的[];arr[i] <=> *(arr+i)。数组当函数的参数就脱变成了指针变量,长度丢失,安全性也变小void func(int * const arr,size_t len);

(1).数组名称不可以被赋值,指针可以被赋值

(2)对数组名称做sizeof计算和对指针做sizeof计算结果不同

(3)对数组名称取地址和对指针变量取地址结果不同

4,数组指针与指针数组:

(1)指针数组:指针数组存放的是指针变量,由多个指针变量结合成一个数组。指针数组的表现形式为char *p[10],根据C语言的优先级,p先跟[]结合,p[10]是一个数组,然后p[10]再跟*结合,变成了*p[10]的指针数组,根据前面的char类型,所以这个是一个存放char* 类型的指针数组。

数组指针(指针):专门用来指向数组的指针。
int arr[10];
int (*p)[10] = arr;
int* p = &num;

#include <stdio.h>
int main(void)
{
	int i = 0;
	char *p[] = {"hello world","123456","abcdefg",};
	for(i = 0;i < 3;i++)
	{
		printf("p[%d] = %s\r\n",i,p[i]);
	}
	return 0;
}

 

(2).指针数组(数组):一个数组里存储的数据类型是指针。把无序的离散数据,归纳到一起。

数组指针的形式是int (*p)[2],根据优先级p先跟星号结合,变成*p,然后再跟[]结合,变成数组指针。数组指针它是一个指针,指向数组。数组指针通常用来访问一个二维数组。
int* arr[3]; <=> int *p1,*p2,*p3;

#include <stdio.h>  //运行结果(p)[0] =2
int main(void)
{
	int i = 0,j = 0;
	int array[3][2] = // 定义一个二维数组
	{
		{0,1},
		{2,3},
		{4,5},
	};
	int (*p)[2] = array; // 定义一个数组指针并指针二维数组的首地址
	p++;                 // 数组指针加1,表示指指针向下移动一行。
	printf("(p)[0] = %d\r\n",*(p)[0]);
	return 0;
}

5,函数指针和指针函数

(1)函数指针: 指向函数的指针(不能解引用)

指向函数的指针:在C语言中函数名代表该函数的首地址,既然函数有地址,那么也可以用指针来指向函数,这种指针叫做函数指针。

#include <stdio.h>
typedef void (*pfunc)(void); // 定义一个函数指针,类型为void ()(void)
void printf_fun(void)
{
	printf("hello world\r\n");
}
int main(void)
{
	pfunc pf = printf_fun; // 定义一个函数指针并指向一个函数
	printf("0x%x\r\n",printf_fun);
	printf("0x%x\r\n",pf);
	pf();  // 通过指针调用函数
	return 0;
}

int sum(int x,int y){return x+y;}  //定义一个函数
int main()                                                 
{
int a=5;int b=6;
int (*p)(int,int); //定义一个函数指针,(*p)()是函数指针的标志。
p=sum;             
 /*指针赋值,这个值就是指向的这个函数。这个和一般指针赋值有所区别,类似于数组名相当于首地址的意思,可以不用取址符&      函数名(SUM )本身有指向函数首地址指针的意义*/
int result=(*p)(a,b);
printf("The result is %d\n",result);
}

(2)指针函数:略

6,二级指针:指向指针的指针,用**p来表示

(1)二级指针可以用来记录指针存储区的地址,只能记录普通存储区地址的指针叫一级指针声明二级指针的时候需要写两个*

(2)在二级指针变量前加**可以表示捆绑的普通变量存储区或里面的数字。

在二级指针变量前加*可以表示捆绑的一级指针存储区或里面的地址数据。

单独使用二级指针变量名称可以表示二级指针本身的存储区或里面的地址数据。

(3)二级指针可以用来代表指针数组,但是不可以代表二维数组。

无类型指针有可能代表一级指针也可能代表二级指针。

二级指针作为函数的形式参数可以让被调用函数使用其他函数的指针类型存储

/*二级指针演示*/
#include <stdio.h>
int main(int argc, char **argv) 
{
	int num = 0;
	for (num = 0;num <= argc - 1;num++) 
	{
		printf("%s\n", *(argv + num));
	}
	return 0;
}

7,const 指针

跨函数使用存储区必须通过指针实现:数组做形式参数的时候真正的形式参数其实是一个指针。

(1)指针存储区可以用来存放函数的返回值

(2)可以采用这种做法让一个函数使用另外一个函数的静态局部变量存储区

(3)不可以把普通局部变量的地址作为返回值使用

(4)可以在声明指针变量的时候使用const关键字

(5)可以在声明指针变量的时候把const关键字写在最前边

(6)不可以通过这种指针对它捆绑的存储区进行赋值

所有用来实现跨函数使用存储区的指针都尽量用这种方法加const关键字。

可以在声明指针变量的时候在指针变量名称前加const关键字。

这种指针本身不可以被赋值,但是可以通过指针对它的捆绑存储区进行赋值。

声明指针变量时可以使用void作为类型名称这种指针叫做无类型指针,这种指针可以和任意类型存储区捆绑无法通过这种指针知道捆绑存储区的类型,不可以在这种指针前面直接加*也不可以用这种指针进行加减整数的计算。这种指针必须首先强制转换成有类型指针然后才能使用这种指针通常用来作为函数的形式参数。

  const int * p; // 不能通过解引用去修改指针所指向的内存的数据
 (1)保护函数的参数
 (2)当指针指向的是只读数据,也应该加上const防止出现段错误。
   int const * p; // 同上

   int* const p; // 不能修改指针变量的值,可以防止指针变量意外变成野指针

    const int* const p; // 既保存指针所指针的内存,也保护指针变量
    int const * const p; // 同上

运行:

*p_num=10
请输入一个数字:1
*p_num是1
*ptr=10

8, 结构体,和指针的区别

struct MyStruct
{
int a;
int b;
int c;
}

MyStruct ss={20,30,40};//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。
MyStruct *ptr=&ss;//声明了一个指向结构对象 ss的指针。类型是MyStruct*,它指向的类型是MyStruct。
int *pstr=(int*)&ss;//声明了一个指向结构对象 ss的指针。它的类型和它指向的类型和ptr是不同的。

A,问怎样通过指针ptr来访问ss的三个成员变量?答案:

ptr->a;

ptr->b;

ptr->c;

B,问怎样通过指针pstr来访问ss的三个成员变量?答案:

*pstr;//访问了ss的成员a。

*(pstr+1);//访问了ss的成员 b。

*(pstr+2)//访问了ss的成员c。

让我们看看怎样通过指针来访问数组的各个单元:

 9,常量指针与变量指针:左数右指

    const int* p; //p可变,p指向的内容不可变

   int const* p; //p可变,p指向的内容不可变

   int* const p; //p不可变,p指向的内容可变

  const int* const p;//p和p指向的内容都不可变8,

三,用法实战

1,结构体里面的指针

typedef struct

{

    float data[MTD_DATA_MAX];                       

//结果数据(复数)设置最大内存64*2?根据“结果数据的脉冲数”与“结果数据的距离数”动态申请内存?

    int32_t doppler_index_scope[2];                 //多普勒序号    

}mtd_param_packet_t;

typedef struct

{

    float data[MTD_DATA_MAX];  

    float time;                               

    int32_t index_scope[2];       

}mtd_param_packet_t;

int point_process( const mtd_param_pool_t  *mtd_param_pool)

{

结构体指针定义:mtd_param_packet_t *mtd_data_packet_sum = NULL;

给指针赋值:mtd_data_packet_sum = (mtd_param_packet_t*)mtd_param_pool->sum_packet;

指针定义:float *mtd_data_sum = NULL;

给指针赋值:mtd_data_sum = (float*)mtd_data_packet_sum->data;

}

typedef struct
{
    radar_pl_cfg_t          pl_cfg;                         
    radar_ps_cfg_t          ps_cfg;
    radar_exe_cfg_t         exe_cfg;
}single_radar_param_t;


typedef struct
{
    uint32_t    fsad;
    uint32_t    fsbb;
}single_wave_param_t;


typedef struct
{
    single_radar_param_t        *current_radar_param_set;      
    single_wave_param_t         *current_wave_param_set;    
    pl_config_t                 *pl_time_line_config;  
}process_param_t;
    process_param下面的结构体current_radar_param_set下面的ps_cfg:
 给指针赋值(加了取地址符号&,因为 radar_ps_cfg_t    ps_cfg这里是结构体定变量定义):
 radar_ps_cfg = (radar_ps_cfg_t*)&process_param->current_radar_param_set->ps_cfg;
给指针赋值(直接用(pl_time_line_config_t*)进行赋值,因为pl_config_t   *pl_time_line_config这里是指针定义):
    pl_time_line_config = (pl_config_t*)process_param->pl_time_line_config;

2,字符转义:把C0换成CC,DD;把CC换成CC AA

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SRC_MAX_LEN (2*1024)
#define DEST_MAX_LEN (2*1024+2)

int parsing(const unsigned char *src_data, int src_len, unsigned char *dest_data, int *dest_len)
{
	if(src_len > SRC_MAX_LEN)
    {
		printf("[%s %d] 非法数据\n", __FUNCTION__, __LINE__);
		return -1;
	}
	int pos = 0;
	dest_data[pos++] = 0xCC;
	for(int i = 0; i < src_len; i++)
    {
		if(0xC0 == src_data[i])
        {
			dest_data[pos++] = 0xCC;
			dest_data[pos++] = 0xDD;
		}
        else if(0xCC == src_data[i])
        {
			dest_data[pos++] = 0xCC;
			dest_data[pos++] = 0xAA;
		}else{
			dest_data[pos++] = src_data[i];
		}
	}
	dest_data[pos++] = 0xC0;
	*dest_len = pos;
	return 0;
}

int main()
{
	unsigned char src_data[SRC_MAX_LEN] = {0x11, 0x22, 0xC0, 0x33, 0xDB, 0x44, 0xC0, 0xC0, 0xDB, 0xDB};
	int src_len = strlen(src_data);
	unsigned char *dest_data = NULL;
	int dest_len;
	dest_data = (unsigned char *)calloc(DEST_MAX_LEN, sizeof(unsigned char));

	parsing(src_data, src_len, dest_data, &dest_len);

	printf("src(长度:%d) ", src_len);
	for(int i = 0; i < src_len; i++)
    {
		printf("%02x ", src_data[i]);
	}
	printf("\n");

	printf("dest(长度:%d) ", dest_len);
	for(int i = 0; i < dest_len; i++)
    {
		printf("%02x ", dest_data[i]);
	}
	printf("\n");
	return 0;
}

3,字符反转义:把DB DC换成C0,把DB DD换成DB

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SRC_MAX_LEN (2*1024)
#define DEST_MAX_LEN (2*1024+2)
int flag_dc=0,flag_dd=0;
int backparsing(const unsigned char *src_data, int src_len, unsigned char *dest_data, int *dest_len)
{
	if(src_len > SRC_MAX_LEN)
	{
		printf("[%s %d] 非法数据\n", __FUNCTION__, __LINE__);
		return -1;
	}
	int pos = 0;
     if( (*src_data==0xC0) && ((*src_data+src_len-1)==0xC0)) )
	 {
		for(int i = 0; i < src_len; i++)
		 {
			if((0xDB == src_data[i])&&(0xDC == src_data[i+1]))
			 {
				 dest_data[pos++] = 0xC0;
					flag_dc=1;
			}
			else if((0xDB == src_data[i])&&(0xDD == src_data[i+1]))
			{
				dest_data[pos++] = 0xDB;
				   flag_dd=1;
				
			}
			else
			{
				if(flag_dc||flag_dd)
				{
					flag_dc=0;
					flag_dd=0;
				}
				else
				{
					dest_data[pos++] = src_data[i];
				}		 
			}
		 }
	}
	else
	{
		printf("非法数据\n");
	}
	*dest_len = pos;
	return 0;
}

int main()
{
	unsigned char src_data[SRC_MAX_LEN] = {0xc0,0x11, 0xdb, 0xdd, 0x33, 0xDB, 0xDC, 0x77, 0xC0, 0xDB, 0xDc,0x22,0xc0};
	int src_len = strlen(src_data);
	unsigned char *dest_data = NULL;
	int dest_len;
	dest_data = (unsigned char *)calloc(DEST_MAX_LEN, sizeof(unsigned char));
	 
	printf("src(长度:%d) ", src_len);
	for(int i = 0; i < src_len; i++)
	{
		 
		printf("%02x ", src_data[i]);
	}
	printf("\n");
	backparsing(src_data, src_len, dest_data, &dest_len);
	printf("dest(长度:%d) ", dest_len);
	for(int i = 0; i < dest_len; i++)
	{
		printf("%02x ", dest_data[i]);
	}
	printf("\n");
	return 0;
}

三,指针的标准定义和赋值方法

四,传值和传址

1,传值,函数调用过程中参数传递的是实参的值,就是把实参传递给形参。对形参的修

改不会影响到实参,这就相当于一个对实参备份的操作,即对形参的修改只是修改实参的备

份,不会影响到实参。

2,传址,函数调用过程中参数传递的是地址,形参和实参共用一个空间,所以对于形参的修改会影响到实参。 

 3,指针地址和指针值的两种表示方法:

#include<stdio.h>
int main()
{
  char *p[5]={"China", "Russia", "England", "France", "America"};
   for(int k=0;k<5;k++)
   {
     printf("*(p+%d)=%s\n",k,*(p+k));
   }
    for(int k=0;k<5;k++)
   {
     printf("p[%d]=%s\n",k,p[k]);//p[k]  <----->  arr[k]  
   }
    for(int k=0;k<5;k++)
   {
     printf("&p[%d]=%p\n",k,&p[k]); 
   }
   for(int k=0;k<5;k++)
   {
     printf("p+%d=%p\n",k,p+k); 
   }
}

 

4,总结:使用指针的好处

 (1)指针可以直接访问内存地址,可以提高效率

 在讲解数组的时候说过,访问数组元素有两种访问形式,一种是下标法,一种是指针法。下标法其实是对指针法的一种封装。当使用下标法访问数组元素的时候,在程序运行的时候最后还是要转换成指针进行访问。所以直接使用指针访问效率会更高。

 (2)在C语言中一些复杂的数据结构可以通过指针来实现。在C语言中的一些复杂结构,比如链表、树二叉树、红黑树等数据结构都是用指针进行构建。

 (3)在C语言中函数传参是值传递的,函数的形参是不可以修改变量值,但是通过指针却可以。 函数传参是不可修改变量的值,但是指针就可以

五, 指针的运算:只允许两种方式,一种是指针自加或自减,一种是指针减指针。

六,指针数组和数组指针:单从字面上似乎很难分清它们是什么,先来看看指针数组和数组指针各自的定义形式。

(1)指针数组的定义形式为:类型名 * 数组名 [ 数组长度 ];例子int *p[8];

(2)数组指针的定义形式为:类型名(*指针名)[数组长度];例子int(*p)[8];

运行结果:

使用数组指针的方式访问二维数组 arr

arr[0][0]=0 arr[0][1]=1 arr[0][2]=2 arr[0][3]=3

arr[1][0]=4 arr[1][1]=5 arr[1][2]=6 arr[1][3]=7

arr[2][0]=8 arr[2][1]=9 arr[2][2]=10 arr[2][3]=11

arr[3][0]=12 arr[3][1]=13 arr[3][2]=14 arr[3][3]=15

使用指针数组的方式访问二维数组 arr

arr[0][0]=0 arr[0][1]=1 arr[0][2]=2 arr[0][3]=3

arr[1][0]=4 arr[1][1]=5 arr[1][2]=6 arr[1][3]=7

arr[2][0]=8 arr[2][1]=9 arr[2][2]=10 arr[2][3]=11

arr[3][0]=12 arr[3][1]=13 arr[3][2]=14 arr[3][3]=15

总结:

A,指针数组:指针数组存放的是指针变量,由多个指针变量结合成一个数组。指针数组的表现形式为char *p[10],根据C语言的优先级,p先跟[]结合,p[10]是一个数组,然后p[10]再跟*结合,变成了*p[10]的指针数组,根据前面的char类型,所以这个是一个存放char* 类型的指针数组。

B, 数组指针:

1、指针类型的数组,首先是数组,数组元素存储的都是指针

2、数组名在表达式中会转换为数组首元素的首地址

3、对数组名使用sizeof关键字或对数组名取地址,&数组名,不会转换首元素的首地址

 

 七,指针函数和函数指针

指针函数:其实是一个简称,是指带指针的函数,它本质上是一个函数,只是返回的是某种类型的指针。其定义的格式为:类型标识符 * 函数名 ( 参数表 )

函数指针:从本质上说是一个指针,只是它指向的不是一般的变量,而是一个函数。因为每个函数都有一个入口地址,函数指针指向的就是函数的入口地址。其定义的格式为:类型标识符 (* 指针变量名)( 形参列表 )

#include <stdio.h>
char* (*fun)(char *str,char *substr);
void input(char *str,char *substr)
{
	printf(" 请输入字符串 :");
	gets(str);
	printf(" 请输入要搜索的字符串 :");
	gets(substr);
}
int strlen(char *str)
{
	int i=0;
	while(str[i]!='\0')
	i++;
	return i;
}
char* serch_str(char *str,char *serch_str)
{
	int i,j,k;
	k = strlen(str) - strlen(serch_str);
	if ( k > 0 && NULL!=str && NULL!=serch_str)
	{
		for ( i = 0; i <= k; i++ )
		{
		  for ( j = i; str[j] == serch_str[j-i]; j++ )
                  {
		     if ( serch_str[j-i+1] == '\0' )
                     {
	    		return str+i+strlen(serch_str);
		     }
		  }
		}
	}
	return NULL;
}
void print(char* ret_str)
{
	if ( ret_str !=NULL )
		printf(" 所搜索字符串之后的字符为 :%s\n",ret_str);
	else
		printf(" 没有找到所要搜索的字符串 \n");
}
void main()
{
	char str1[50],str2[50];
	char serch_str1[50],serch_str2[50];
	char* ret_str1,* ret_str2;
	input(str1,serch_str1);
	ret_str1 = serch_str(str1,serch_str1);
	printf(" 直接调用函数 serch_str()\n");
	print(ret_str1);
	input(str2,serch_str2);
	fun = serch_str;
	ret_str2 = fun(str2,serch_str2);
	printf(" 使用函数指针 fun 调用函数 serch_str()\n");
	print(ret_str2);
	return ;
}
/*
运行结果:
请输入字符串 :Never forget to say thanks!
请输入要搜索的字符串 :say
直接调用函数 serch_str()
所搜索字符串之后的字符为 : thanks!
请输入字符串 :Keep on going never give up!
请输入要搜索的字符串 :going
使用函数指针 fun 调用函数 serch_str()
所搜索字符串之后的字符为 :never give up!
*/

 1,函数指针

指向函数的指针:在C语言中函数名代表该函数的首地址,既然函数有地址,那么也可以用指针来指向函数,这种指针叫做函数指针。

 运行结果:

0x400566

0x400566

hello world

指针函数

指针函数, 即返回值为指针的函数, 本质上是一个函数。指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。

声明格式为:*类型标识符 函数名(参数表)

所谓的指针函数和普通函数对比不过就是其返回了一个指针(即地址值)而已。

 八,二级指针练习**p

1,二级指针可以用来记录指针存储区的地址,只能记录普通存储区地址的指针叫一级指针声明二级指针的时候需要写两个*

2,在二级指针变量前加**可以表示捆绑的普通变量存储区或里面的数字。

在二级指针变量前加*可以表示捆绑的一级指针存储区或里面的地址数据。

单独使用二级指针变量名称可以表示二级指针本身的存储区或里面的地址数据。

3,二级指针可以用来代表指针数组,但是不可以代表二维数组。

无类型指针有可能代表一级指针也可能代表二级指针。

二级指针作为函数的形式参数可以让被调用函数使用其他函数的指针类型存储区。

 九,堆和栈

堆和栈在使用时“生长”方向相反,栈向低地址方向“生长”,而堆向高地址方向“生长”。

1,栈,是硬件,主要作用表现为一种数据结构,是只能在一端插入和删除数据的特殊线性

表。允许进行插入和删除操作的一端称为栈顶,另一端为栈底。栈按照后进先出的原则存储

数据,最先进入的数据被压入栈底,最后进入的数据在栈顶,需要读数据时从栈顶开始弹出

数据。栈底固定,而栈顶浮动。栈中元素个数为零时称为空栈。插入一般称为进栈(push),

删除则称为出栈(pop)。 栈也被称为先进后出表,在函数调用的时候用于存储断点,在递归时也要用到栈。

在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。在 i386 机器中,栈顶由称为 esp 的寄存器进行定位。压栈的操作使栈顶的地址减小,弹出的操作使栈顶的地址增大。栈保存了一个函数调用时所需要的维护信息,这常常被称为堆栈帧。栈一般包含以下两方面的信息:

(1)函数的返回地址和参数。

(2)临时变量:包括函数的非静态局部变量及编译器自动生成的其他临时变量。

2,堆,是一种动态存储结构,实际上就是数据段中的自由存储区,它是 C 语言中使用的一种名称,常常用于存储、分配动态数据。堆中存入的数据地址向增加方向变动。堆可以不断进行分配直到没有堆空间为止,也可以随时进行释放、再分配,不存在顺序问题。堆内存的分配常通过 malloc()、calloc()、realloc() 三个函数来实现。而堆内存的释放则使用 free() 函数。C语言中用于动态申请内存空间的函数主要malloc()函数,calloc()函数和realloc()函数:

(1)void *malloc(size_t size);

功能:在内存空间堆区中申请一段size字节大小的连续存储空间。

参数:size 为需要申请内存空间的大小,单位为Byte.

返回值:若内存空间申请成功,返回指向该内存空间的首地址;否则,返回NULL.

(2)void *calloc(size_t n, size_t size);

功能:在内存空间堆区中申请一段n个size字节大小的连续存储空间,总空间大小即为 n*size Bytes.

参数:n 为需要申请size字节大小的个数;size为每个单元的字节大小。

返回值:若内存空间申请成功,返回指向该内存空间的首地址;否则,返回NULL.

(3)void *realloc(void *_ptr, size_t size);

功能:realloc函数,顾名思义为重置(re*)一段内存空间的大小,可使该内存空间扩容或缩容。

参数:_ptr 为需要重置内存空间的首地址;size为重置后该内存空间的容量。

返回值:若重置成功时,返回可为原内存空间的首地址,也可为一段新的内存空间首地址;失败时返回NULL。

3,三个函数相同处与不同处

(1)三个函数的相同之处:都是在堆区上申请一段连续的存储空间;

使用malloc, calloc, realloc三个函数主动申请的内存空间在使用完后,都需要调用 free(void *ptr) 函数进行主动释放该内存空间。

malloc()函数申请的内存空间默认不会被清空的,因此有两种情况:若是在一段从未使用过的空间上进行申请的话,则申请成功后该内存空间均为0值;若是申请所得的内存空间是一段之前被使用过的空间的话,则此时该空间上的值可能会是一些奇奇怪怪的随机值。

(2)三个函数的不同之处:

calloc()函数所申请的内存空间默认是会被清空的,所以使用calloc()函数申请的内存空间其默认值都为0值。

realloc()函数其工作原理为:若是将原内存空间进行缩容的话,realloc仅仅改变了内存空间的索引信息即可。若是将原内存空间进行扩容的话,

《1》先是在原内存空间的后面进行探索寻找,看是否有满足要求的一段连续存储空间,若是有则表示申请成功,直接返回原来内存空间首地址即可,若是没有则进行下一步;

《2》在一段新的存储空间上进行申请,申请一段满足需求大小的连续存储空间,同时还会进行 将原空间上的值悉数拷贝到新内存空间的相应位置上来 以及主动释放掉原来内存空间 等一系列操作;

《3》使用realloc函数申请失败的话,返回NULL;此时原内存空间仍有效。

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *a = (int *)malloc(sizeof(int) * 5);
    int *b = (int *)calloc(5, sizeof(int));
    printf("\na value:\n");
    for(int i = 0; i < 5; i++)
	{
        printf("a[%d] = %d\n", i, *(a+i));
    }
    printf("\nb value:\n");
    for(int i = 0; i < 5; i++)
	{
        printf("b[%d] = %d\n", i, *(b+i));
    }
    int *temp = (int *)realloc(a, 10);
    if(temp == NULL) // realloc 失败 a所指空间仍有效
    {
        printf("\nafter realloc a value:\n");
        for(int i = 0; i < 5; i++)
		{
            printf("a[%d] = %d\n", i, *(a+i));
        }    
    }
    else  // realloc 成功,a所指内存空间已被释放,将a重新指向新的内存空间首地址
    {
        a = temp;
        printf("realloc success a value:\n");
        for(int i = 0; i < 10; i++)
		{
            i && printf(" ");
            printf("%d", *(a+i));
        }
        printf("\n");
    }
    return 0;
}

运行结果: 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/675963.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

冯喜运:6.3黄金原油晚间最新行情及独家操作策略指导

【黄金消息面分析】&#xff1a;在全球经济的波动和不确定性中&#xff0c;黄金作为传统的避险资产&#xff0c;其价格走势和市场分析一直是投资者关注的焦点。本周一&#xff08;北京时间6月3日&#xff09;&#xff0c;现货黄金价格基本持平&#xff0c;交易商正在等待本周公…

Leecode---技巧---颜色分类、下一个排列、寻找重复数

思路&#xff1a; 遍历一遍记录0,1,2的个数&#xff0c;然后再遍历一次&#xff0c;按照0,1,2的个数修改nums即可。 class Solution { public:void sortColors(vector<int>& nums){int n0 0, n1 0, n2 0;for(int x: nums){if(x0) n0;else if(x1) n1;else n2;}for…

机器学习第十一次课

前言 从现在开始进入神经网络的领域了 正文 先是一段历史介绍,这个就跳过吧,我觉得这里最重要的就是反向传播这里 反向传播 反向传播&#xff08;Backpropagation&#xff09;是一种训练人工神经网络的算法&#xff0c;它通过计算损失函数关于网络参数的梯度来调整网络参数…

MySQL事务与并发控制案例

目录 1. MySQL在事务与并发控制情况下加锁案例实现 2. 锁超时或死锁怎么办&#xff1f; 1. MySQL在事务与并发控制情况下加锁案例实现 第一步&#xff1a;开启一个事务并发锁 第二步&#xff1a;对加X锁&#xff08;排他锁&#xff09;的数据进行操作 可以看到锁被阻塞了&am…

YOLOv10训练教程—用YOLOv10训练自己的数据集

文章目录 YOLOv10简介亮点模型介绍 下载源码环境配置准备数据集训练模型&#xff1a;命令行py文件 验证模型推理参考文献 ✨✨✨✨立志真正解决大家问题&#xff0c;只写精品博客文章&#xff0c;感谢关注&#xff0c;共同进步✨✨✨✨ YOLOv9还没捂热乎&#xff0c;YOLOv10就推…

初中英语优秀作文分析-003My Favorite Movie Type-我最喜欢的电影类型

PDF格式公众号回复关键字:SHCZYF003 记忆树 1 I’d like to share my favorite movie type with you. 翻译 我想和你分享我最喜欢的电影类型。 简化记忆 电影类型 句子结构 I 主语 我&#xff0c;would 情态动词 愿意做某事&#xff0c;like 谓语 喜欢&#xff0c;to sha…

zynq-7015启动分析及裸机BootLoader编写(未完待续)

使用lwip-tcp远程对QSPI进行更新、QSPI FLASH启动 W25Q128资料&#xff1a; W25Q128JV datasheet(1/78 Pages) WINBOND | 3V 128M-bit serial flash memory with dual/quad spi (alldatasheet.com) UG585资料&#xff1a; Zynq 7000 SoC Technical Reference Manual-UG585 翻译…

SQL进阶day9————聚合与分组

目录 1聚合函数 1.1SQL类别高难度试卷得分的截断平均值 1.2统计作答次数 1.3 得分不小于平均分的最低分 2 分组查询 2.1平均活跃天数和月活人数 2.2 月总刷题数和日均刷题数 2.3未完成试卷数大于1的有效用户 1聚合函数 1.1SQL类别高难度试卷得分的截断平均值 我的错误…

JUC从实战到源码:悲观锁和乐观锁真正了解了吗

【JUC】- 多线程与锁的知识 &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f3c6; 博客首页 怒放吧德德 To记录领地 &#x1f31d;分享学习心得&#xff0c;欢迎指…

语音群呼之语音导航的应用

在数字化时代&#xff0c;语音群呼技术已成为企业、组织和个人高效沟通的重要工具。语音群呼不仅能够快速地将信息传递给目标群体&#xff0c;而且通过语音导航功能&#xff0c;还能确保信息传达的准确性和用户体验的优质性。本文将深入探讨语音群呼的语音导航功能&#xff0c;…

三菱M5-559 KURU TOGA advance断芯清理维修

三菱559自动铅笔使用过程中突然不出芯了&#xff0c;后面装芯出不来&#xff0c;前面插笔芯进不去&#xff0c;网上搜索&#xff0c;发现这支笔要按照下面的方法拆开清理&#xff0c;这里记录一下方便大家查看&#xff1a; 1、拧掉笔头外面的罩子。 2、要大胆一点&#xff0c…

图形学初识--深度测试

文章目录 前言正文为什么要有深度测试&#xff1f;画家算法循环遮挡 深度测试当代最常见实现方式&#xff1f;总述什么是z-buffer呢&#xff1f;z-buffer从哪来呢&#xff1f;如何利用z-buffer实现深度测试&#xff1f;举个例子 结尾&#xff1a;喜欢的小伙伴点点关注赞哦! 前言…

【MyBatis】MyBatis操作数据库(二):动态SQL、#{}与${}的区别

目录 一、 动态SQL1.1 \<if>标签1.2 \<trim>标签1.3 \<where>标签1.4 \<set>标签1.5 \<foreach>标签1.6 \<include>标签 二、 #{}与${}的区别2.1 #{}是预编译sql&#xff0c;${}是即时sql2.2 SQL注入2.3 #{}性能高于${}2.4 ${}用于排序功能…

SpringBoot案例,通关版

项目目录 此项目为了伙伴们可以快速入手SpringBoot项目,全网最详细的版本,每个伙伴都可以学会,这个项目每一步都会带大家做,学完后可以保证熟悉SpringBoot的开发流程项目介绍:项目使用springboot mybatis进行开发带你一起写小项目先把初始环境给你们第一步新建springboot项目返…

短剧出海的优势分析

海外短剧作为一种新兴的内容形式&#xff0c;正以其独特的魅力迅速占领市场&#xff0c;为企业带来了前所未有的商业机遇。本文将深入探讨短剧出海的优势&#xff0c;并为企业和老板们提供实用的操作指南。短剧出海是一个包含多个步骤的复杂过程&#xff0c;短剧出海需要综合考…

第100天:权限提升-数据库RedisPostgre第三方软件TV向日葵服务类

目录 思维导图 案例一: 数据库-Redis 数据库权限提升-计划任务 案例二: 数据库-PostgreSQL 数据库权限提升-漏洞 PostgreSQL 提权漏洞&#xff08;CVE-2018-1058&#xff09; PostgreSQL 高权限命令执行漏洞&#xff08;CVE-2019-9193&#xff09; 案例三: 三方应用-…

使用system verilog进行流水灯和VGA打印字符

使用system verilog进行流水灯和VGA打印字符 目录 **使用system verilog进行流水灯和VGA打印字符****system verilog的优点****VGA程序编写**VGA 控制器模块字符生成模块顶层模块测试基准程序**效果** **流水灯程序设计****效果** **总结** system verilog的优点 面向对象编程…

C# WinForm —— 27 28 29 30 ListView 介绍与应用

1. 简介 和ListBox的外观类似&#xff0c;都可以多列显示&#xff0c;但 ListView 功能更强大&#xff0c;提供了5种不同的显示方式 2. 属性 属性解释(Name)控件ID&#xff0c;在代码里引用的时候会用到Enabled控件是否启用CheckBoxes复选框是否显示在项旁边ContextMenuStri…

浏览器渲染优--防抖节流懒加载

合理选择css选择器 相比于.content-title-span&#xff0c;使用.content .title span时&#xff0c;浏览器计算样式所要花费的时间更多。使用后面一种规则&#xff0c;浏览器必须遍历页面上所有 span 元素&#xff0c;先过滤掉祖先元素不是.title的&#xff0c;再过滤掉.title…

拿笔记下来!产品采购制造类合同怎样写比较稳妥?

拿笔记下来&#xff01;产品采购制造类合同怎样写比较稳妥&#xff1f; 近日&#xff0c;几经波折&#xff0c;泰中两国终于完成了潜艇采购谈判&#xff01;你知道吗&#xff1f;产品制造类合同或协议在起草前如果没有充分考虑各种因素&#xff0c;可能会导致一系列问题和不利…