C基础指针的使用

Posted mChenys

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C基础指针的使用相关的知识,希望对你有一定的参考价值。

目录

一、一级指针

指针也是一个变量,指针存放的内容是一个内存地址,该地址指向一块内存空间。

1.1 指针变量的定义

一级指针变量的数据类型会在基本数据了类型之后多了一个*号,指针变量只能存放内存地址(一个16进制的数),不能将一个基本数据类型直接赋值给一个指针变量。

如果要取出一级指针变量指向的内存地址所对应的值的话,可以通过在指针变量前加一个*号来获取

int *p;//表示定义一个指针变量p, 类型是int *;
*p;//代表获取该指针指变量指向的内存块对应的实际数据

int *p = 100; //错误, 只能指向内存地址, 是一个16进制的数
*p =100 ;  //正确, 因为*p操作的是变量的值.

1.2 &取地址运算符

通过&符号可以取得一个变量在内存当中的地址,然后就可以赋值给指针变量了。

#include<stdio.h>

int main()

	int a = 100;
	int *p;// 定义了一个int *类型的指针变量,名字叫做p
	p = &a;// 给指针变量赋值,把a的内存地址赋值给指针变量p
	*p = 200;//修改a的值,效果等于直接给a赋值, *p代表指针指向变量的值,而p代表指向变量的内存地址
	printf("p=%p,*p=%d,a=%d\\n",p,*p,a); 

    // 通常为了书写方便,可以直接这样给指针变量赋值
    // int *p1 = &a;
    
    // 直接修改a变量的值,内存地址并不会修改
    a = 50;
    // 查看*p的值
    printf("p=%p,*p=%d,a=%d\\n",p,*p,a);

	return 0;

两次输出结果如下:

可以看到*p和a变量的值是一样的,并且修改值并不会影响p指针变量指向的内存地址。

1.3 无类型指针

定义一个指针变量,但不指定它指向具体哪种数据类型。可以通过强制转化将void *转化为其他类型指针,也可以用(void *)将其他类型指针强制转化为void类型指针。
void *p 他可以指向任意类型的内存地址

1.4 空指针与野指针

NULL在C语言中的定义为(void *)0 ,它是一个宏常量, #define NULL 0 .如果一个指针变量没有明确指向一块内存地址, 那么就把这个变量指向NULL,这个指针就是空指针,空指针是合法的,例如:

int *p = NULL;
// 或者
int *p;
p = NULL;

指向NULL的指针叫空指针,没有具体指向任何变量地址的指针(也就是没有初始化值的指针)叫野指针。

#include<stdio.h>

int main()

	// 定义了一个int *类型的指针变量p
	int *p;  
	// 如果指针变量没有初始化就使用,那就是野指针,
	// 由于它不知道指向的内存地址是啥,所以无法修改这块内存地址所对应的数值, 程序执行的时候会报错
	*p = 100; 

	return 0 ;

1.5 指针的兼容性

指针之间赋值比普通数据类型赋值检查更为严格,例如:不可以把一个double *赋值给int * ,它是不会自动类型转换的.
原则上一定是相同类型的指针指向相同类型的变量地址,不能用一种类型的指针指向另一种类型的变量地址。

1.6 常量指针与指针常量

1、指向常量的指针(常量指针)
常量指针的const书写在指针类型之前,例如:
const char *p;
定义一个指向常量的指针, 但是这个指针不能通过*p的方式取修改变量的值了。但是可以通过*p的方式读取变量的值。

#include<stdio.h>

int main()

	int a = 10; //定义一个变量

	// 定义一个指向int类型的地址的指针变量,指向a变量的内存地址
	const int *p = &a;   // 指针类型是 const int *
	*p = 100; // 编译会通过不了,常量指针的常量不能改变, 但是直接给a变量赋值还是可以的.只是不允许通过*p的方式
	printf("a=%d\\n",*p);
	return 0;


2、指针常量
指针常量的const书写在指针类型之后,例如:
char * const p;
定义一个指针常量,一旦初始化之后其内容不可改变,也就是说不能再指向其他变量的地址了。但是可以通过*p的方式改变变量的值。

#include<stdio.h>

int main()

	int a = 10; //定义一个变量

	//定义一个指针常量,指向a变量的内存地址
	int *const p = &a; //指针类型是 int *const

	int b = 20; // 允许修改变量
	p = (int *)&b;//编译通不过,因为p是一个常量,不能被修改,指针常量的指针不能修改

	printf("%d\\n",*p);

	return 0;

原则上在C语言中常量指针的常量不能修改、指针常量的指针不能修改;但是C语言中通过const定义的常量是有问题的,因为可以通过指针变量来间接的修改常量的值,所以在C语言中用#define常量比较多,而C++的const是没办法改的。

1.7 指针与数组的关系

一个变量有地址,一个数组包含若干个元素,每个元素在内存中都有地址,而且是连续的内存地址.也就是说如果数组是char类型的,那么每个元素的内存地址就是相差1, 如果是int类型的数组, 那么元素间的内存地址是相差4,例如:

int a[10];
int *p = a;

p和&a[0]的地址,以及a数组名的地址都是一样的。 也就是说数组名的地址等于数组首元素的内存地址。

#include<stdio.h>

int main()

	char a[10];
	printf("a=%p\\na[0]=%p\\na[1]=%p\\na[2]=%p\\n",a,&a[0],&a[1],&a[2]);

	printf("========================\\n");

	int b[10];
	printf("b=%p\\nb[0]=%p\\nb[1]=%p\\nb[2]=%p\\n",b,&b[0],&b[1],&b[2]);
	return 0;


输出结果如下:

a=0x16efa760e
a[0]=0x16efa760e
a[1]=0x16efa760f
a[2]=0x16efa7610
========================
b=0x16efa75e4
b[0]=0x16efa75e4
b[1]=0x16efa75e8
b[2]=0x16efa75ec

从结果也可以看出char数组的每个元素内存地址相差1, int数组的元素内存地址相差4.

注意:当指针变量指向一个数组名的时候,C语言语法规定指针变量名可以当做数组名使用.但是指针变量的sizeof不是数组的sizeof, 指针变量的sizeof在64位系统是8个字节, 32位系统是4个字节.

#include<stdio.h>

int main() 
    int a[] = 1, 2, 3, 4, 5;
    int *p; // 指针变量
    // 数组可以直接赋值给指针变量,不用&取地址,因为变量名a的地址就是数组首元素的内存地址
    p = a;

    // 如果指针指向的是数组,那么变量名可以当做数组用
    p[3] = 100;

    // 数组的长度和指针变量的长度是不一样的
    printf("数组长度=%lu字节,指针变量长度=%lu字节\\n", sizeof(a), sizeof(p));

    // 操作指针变量来操作数组元素
    int i;
    for (i = 0; i < 5; i++) 
        printf("a[%d]=%d\\n", i, p[i]);// 和操作数组一样操作元素
    
    return 0;


输出结果如下:

数组长度=20字节,指针变量长度=8字节
a[0]=1
a[1]=2
a[2]=3
a[3]=100
a[4]=5

注意:指针变量如果指向的不是数组名或者数组的首元素的内存地址,那么用指针去取数组的元素的时候是有区别的,假设指针指向的是数组下标为2的元素的内存地址, 那么p[0] 就不再是等于a[0]了, 而是等于a[2]了, 同理p[1]就应该等于a[3],其他依次类推。

1.8 指针位移运算

指针运算不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数做为倍数的运算。
例如:char *p;当执行p++;后,移动的字节数是sizeof(char)这么多的字节数;如果是int *p1;那么p1++后移动的字节数就是sizeof(int)

#include<stdio.h>
int main()

    int a[5] = 0;
    int *p = a;//指向数组名的指针,就是数组首元素的地址
    *p = 100;// 给指针变量对应地址的变量赋值100,相当于p[0] = 100;

    printf("移动前指针位置:%p\\n",p);

    //移动指针
    //移动2,表示移动了sizeof(int) *2 个字节,对应就是数组下标=2的元素内存地址
    p += 2;
    *p = 20;//修改a[2]的值为20
    printf("移动后指针位置:%p\\n",p);

    int i;
    for(i = 0 ;i < 5; i++)
    
        printf("a[%d]=%d\\n",i,a[i]);
    
    return 0;


结果如下:

移动前指针位置:0x16efbb600
移动后指针位置:0x16efbb608
a[0]=100
a[1]=0
a[2]=20
a[3]=0
a[4]=0

可以看到指针+2后,移动前后的内存地址刚好相差8个Byte,也就是2个int元素的占用的内存大小.

1.9 指针与char数组

在C语言中所有的数据类型都可以当做是一个char的数组.

#include<stdio.h>

int main()

	int a = 0x12345678; //定义一个16进制数,int占4个字节,相当于char数组长度是4个BYTE

	char *p = (char *)&a;//定义char * 的指针,指向a的地址

	int i = 0 ;
	for(i =0;i < sizeof(a); i++)
	
		printf("a[%d]=%x\\n",i,p[i]);//由于是小端对齐,所以输出结果是78,56,34,12倒着输出的.内存地址从左->右表示从高->底
	


	return 0;

练习-将一个int数转成ip地址
我们知道一个int占4个字节,刚好对应4个char, 对于unsigned char的取值范围就是0~255, 所以刚好和ip地址每段的取值符合.所以可以将int数转成ip地址。

#include<stdio.h>

int main()

	int a = 987654321;

	unsigned char *p = (unsigned char *)&a; //定义unsigned char *类型的指针变量p 对应a变量的地址,这里其实是指针强转
	
	//在C语言中基本数据类型可以当做char数组对待,所以可以这样操作
	printf("%u.%u.%u.%u\\n",p[3],p[2],p[1],p[0]); // 结果为: 58.222.104.177

	return 0;

直接ping 987654321 对应的ip地址就是上面用程序求出的ip: 58.222.104.177

练习-将一个字符串IP转成unsigned int类型.
这个案例就是上面案例的反过程, 先将字符串中每一段的数据取出来,然后操作指针来赋值.

#include<stdio.h>

int main()

	char a[] = "192.168.1.1";
	unsigned int ip = 0;
	unsigned char * p = (unsigned char *) &ip;

	//先从字符串中取值
	int a1,a2,a3,a4;
	sscanf(a,"%d.%d.%d.%d",&a1,&a2,&a3,&a4);

	//小端对齐赋值
	p[0] = a4;
	p[1] = a3;
	p[2] = a2;
	p[3] = a1;

	printf("ip=%u\\n",ip); // ip=3232235777
	return 0;

如下所示,对数字3232235777进行ping时,查看的ip刚好就是192.168.1.1

练习-使用指针对二维数组进行排序

#include<stdio.h>

int main()

	int a[2][3] = 10,21,13,42,5,9; //二维数组

	int *p = (int *)a; // 定义一个指针, 指向数组a
	int i,j;

    //下面通过操作指针的方式来排序, 由于内存是连续的,所以可以当做一维数组看待
	int size = sizeof(a) / sizeof(a[0][0]); // 6
	for(i = 0 ; i < size; i++)
	
		for(j=0 ; j< size - i -1 ; j++)
		
			if(p[j] > p[j+1])
			
       			// 直接通过指针操作内存地址对应的值
				int temp = p[j];
				p[j] = p[j+1];
				p[j+1] = temp;
			
		
	
	//输出排序后的结果
	for( i =0 ; i <sizeof(a) /sizeof(a[0]) ; i++)
	
		for( j =0; j<sizeof(a[0]) / sizeof(a[0][0]); j++)
		
			printf("%d,",a[i][j]); // 5,9,10,13,21,42,
		
	
	
	return 0;

练习-使用指针对字符串取反操作

int main() 
    char str[20] = "hello world";

    char *start = str;//&(str[0]);  //创建首指针
    char *end = start + strlen(str) - 1; //创建尾指针, 通过指针偏移到尾部

    while (start < end) 
        // 通过指针操作值
        char tmp = *start;
        *start = *end;
        *end = tmp;
        // 偏移首位指针
        start++;
        end--;
    
    printf("%s\\n", str); // dlrow olleh

    return 0;


1.10 指针数组

由于指针是一个变量,所以也可以以数组的形式出现。 指针数组的字节长度不受数据类型的影响,而是受到操作系统的影响, 在32位系统中,一个指针的长度是4个BYTE, 64位系统是8个BYTE, 所以32位系统中指针数组的长度等于 元素个数*4 ; 64位系统中指针数组的长度等于元素个数*8.

#include<stdio.h>

int main()

	//定义一个int *类型的10个元素的指针数组
	int *a[10] = NULL;

	//定义一个char *类型的10个元素的指针数组
	char *b[10] = NULL;

	printf("%lu,%lu\\n",sizeof(a),sizeof(b)); // 80,80

	return 0;

指针数组元素的赋值

#include<stdio.h>

int main()

	int *a[10] = NULL;
	
	int i = 5;
	a[0] = &i; //取i的地址给a[0]指针

	*a[0] = 100; //修改i的值

	printf("i=%d\\n",i); // i=100
	
	return 0;

二、二级和多级指针

指针就是一个变量,既然是变量就也就存在内存地址,所以可以定义一个指向指针的指针变量。二级指针的定义会比一级指针多一个*;
二级指针指向的是一级指针的地址, 一级指针指向的是变量的地址, 如下图所示:

换成代码就是:

int a =  10;
int *p = &a; //一级指针指向变量a
int **pp = &p; //二级指针指向一级指针变量p

//反过来就是修改变量的值
*pp  = NULL; // 表示将1级指针变量的值赋值为NULL;
**pp  = 100; //表示把变量a的值赋值为100; 

2.1 二级指针与一级指针数组的关系

一级指针数组里面存放的元素都是一级指针变量, 而指针数组的名字就是数组首元素的内存地址, 也就是一级指针的内存地址, 所以可以用二级指针的来指向.当指针指向数组的时候就可以当做数组本身来使用了.

int main()

	int *a[3] = NULL;//一级指针数组
	int x,y,z;
	a[0] = &x; //给数组元素赋值
	a[1] = &y;
	a[2] = &z;

	int **p = a;//二级指针指向数组名,数组名等效于指向&a[0],而a[0]是个一级指针.所以指向一级指针地址的指针那就是二级指针了.
		
	//指针指向数组就可以当做数组用

	int i;
	for(i=0;i<3;i++)
	
		printf("p[%d]=%p\\n",i,p[i]); //打印数组元素,数组元素是一级指针
	
	
	*p[0] = 100;//等效于 *a[0] =100; 就是将0下标的指针指向的变量的值修改为100,因为p[0]=a[0]=一级指针,然后加*就是修改值
	*p[1] = 200;
	*a[2] = 300;
	printf("x=%d,y=%d,z=%d\\n",x,y,z);

	
	return 0;

结果如下:

p[0]=0x16b1735f8
p[1]=0x16b1735f4
p[2]=0x16b17

以上是关于C基础指针的使用的主要内容,如果未能解决你的问题,请参考以下文章

C语言 将函数名作为参数被另外一个函数调用

数组名作函数参数时,实参与形参变量之间的数据传递是?

当用数组名作形参时,形参数组改变可使实参数组随之改变 . 这句话哪里错了 求详细解答

数组名作为参数转为指针

数组名作为函数参数

函数名作为参数传递与回调函数