C语言指针用法完善篇

2023-01-19 14:49:37

一,指针定义:

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; // 野指针
使用野指针的后果:段错误。注意:野指针是无法分辨的,所以比空指针危害更。
如何避免野指针造成的错误?
所有的野指针都人制造出来的,只要人人都不制造野指针就会有野指针造成的错误。

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

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

二,指针的作用

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

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

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

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

三,指针的好处:

(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;
}

  • 作者:寒听雪落
  • 原文链接:https://blog.csdn.net/wangjie36/article/details/108116964
    更新时间:2023-01-19 14:49:37