指针相关内容(C语言)

本文详细介绍了C语言中的指针概念,包括指针变量、指针运算和指针与数组的关系。指针用于存储内存地址,运算包括地址增减。在数组上下文中,指针与数组名有微妙区别,数组名是地址常量。指针也可用于多维数组的访问。此外,文章还讨论了函数指针和指针函数的使用,以及字符串处理中的指针应用。最后提到了常量指针和野指针的概念。
摘要由CSDN通过智能技术生成

1、什么是指针

在内存中,每一个地址单元,都有一个编号,称为地址。在虚拟内存中,也同样如此。

专门用来存放地址的变量称为指针变量,简称指针。

不管是什么类型的指针变量,在32位机上,占4个字节;在64位机上,占8个字节

2、指针运算

(1)p是一个 指针变量;p + n 或者  p - n ,实际得到的地址量是

        p + / - sizeof(* p)  *n

(2)不同数据类型的两个指针进行加减运算是无意义的

        p 、q 是两个相同类型的指针变量;

        p - q 得到的是 (p的地址在 - q的地址值)/sizeof(*p)

(3)指针是一个地址值,可以进行关系运算。同理,不同类型的指针进行该运算没有意义。

3、指针和数组

(1)指针和数组名本质的区别是指针是指针是一个变量,用来储存地址,数组名是地址常量;

数组名是地址常量,数组名的类型是数组元素的指针类型。

在把数组名当成一个地址使用的时候,和&数组名[0]是等价的。

更进一步的讲,比如 int arr[5]={0};

arr+ i 和 &arr[i]是等价的,因此 *(arr + i) 和 arr[i]是等价的。对数组的下标操作本质是地址操作,因此,i[arr] 和 arr[i] 其实是一样的。

在把数组名当一个数组类型使用的时候,才更符合我们常规意义上的数组概念。

比如 sizeof(arr) 和 &arr。

#include <stdio.h>
#include <string.h>
int main(void)
{
	int arr[5] = {0,1,2,3,4};
	int *p1 = arr; //arr作为地址使用,类型是 int *型 和 &arr[0] 等价
	int *p2 = &arr[0];
	printf("%p,%p\n",p1,p2);

	printf("%d\n",sizeof(arr));//arr作为数组类型使用
	int (*p3)[5] = &arr;//arr作为数组类型使用,对arr取地址,得到的是一个指向数组的指针                    
	printf("%p\n",p3);//值和&arr[0]相同,但类型是不一样的

	return 0;
}

 

 

 (2)在涉及多维数组和指针之前,得先区分一下数组指针和指针数组。

顾名思义,数组指针是一个指向数组的指针,形式如 int (*p)[5],

而指针数组是一个数组元素为指针的数组,形式如 int *p[5].        

#include <stdio.h>

int main(void)
{
	int arr[3][3]={{1,2,3},{4,5,6},{7,8,9}};

	int (*p1)[3]=arr;//数组指针
	printf("%p\n",p1);
	for(int (*row)[3] = arr;row < arr+3; ++row){
		printf("%p:",row);
		for(int *line = *row;line < *row + 3; ++line){
			printf("%d\t",*line);
		}
		printf("\n");
	}

	p1 = &arr[0];//arr作为地址,类型和值都和&arr[0]相同
	printf("%p\n",p1);
	for(int (*row)[3] = &arr[0];row < &arr[3]; ++row){
		printf("%p:",row);
		for(int *line = &(*row)[0];line < &(*row)[3]; ++line){
			printf("%d\t",*line);
		}
		printf("\n");
	}
    //以上两种方式直接通过指针变量的方式访问多维数组元素
    for(int i = 0; i < 3; i++){
        printf("%p:",&arr[i]);
        for(int j = 0; j < 3; j++){
            printf("%d%d%d\t",arr[i][j],*(arr[i]+j),*(*(arr+i)+j));
        }
        printf("\n");
    }
    //所以,arr+i 和 &arr[i],*(arr + i)和 arr[i] 是等价的
    //*(arr + i) + j、arr[i] + j、 &arr[i][j]是等价的 
    //*(*(arr+i)+j) 、 *(arr[i] + j) 、 arr[i][j]是等价的
    //或者说,arr[i][j]的下标写法只是对某个具体数数组元素的访问更方便,
	//本质上编译器其实对数组下标的写法会自动变成地址操作

	int arr1[3] = {1,2,3};
	int arr2[3] = {4,5,6};
	int arr3[3] = {7,8,9};
	int *p2[3] = {arr1,arr2,arr3};//指针数组
	printf("%p\n%p\n%p\n",p2[0],p2[1],p2[2]);
	for(int i = 0; i<3 ;i++)
	{
		printf("%p:",*(p2 + i));
		for(int *line = *(p2 + i); line <  *(p2 + i) +3; ++line)
			printf("%d\t",*line);
		printf("\n");
	}

	return 0;
}

 (3)多维数组的存储方式

c语言本质上其实只有一维数组,数组元素的访问本质都是数组元素地址的解引用

#include <stdio.h>
typedef int (*ptr)[4];
int main(void)
{
	int arr1[12] = {1,2,3,4,5,6,7,8,9,10,11,12};
	int (*p1)[4] = NULL;
	p1 = (ptr)arr1;//把 arr1类型强转,但其实不强转程序运行结果也是一样的,就是warning类型不一致
	for(int i = 0;i < 3;i++){
		for(int j = 0;j < 4;j++)
		{
			printf("%d,%d\t",*(*(p1+i)+j),p1[i][j]);	
		}
		printf("\n");
	}
	//一、arr并不是二维数组,但是使用数组指针也能访问数组元素
	//二、p1并不是二维数组,但是使用p[i][j]也能访问对应元素
	int arr2[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
	int *p2 = (int *)arr2;
	for(int i = 0; i < 12; i++)
	{
		printf("%d,%d\t",p2[i],*(p2+i));
	}
	printf("\n");
	
	return 0;
}

 2、指针和函数

(1)函数指针

函数的地址:如果在程序中定义了一个函数,那么在编译时,系统会为该函数代码分配存储空间,这段空间的首地址被称为函数的地址,而且函数名表示的就是该地址。因此我们可以定义一个指针变量来存放函数地址,这个指针变量就叫函数指针变量,简称函数指针。

#include <stdio.h>

int max(int ,int );
int main(void)
{
	int (*fp)(int,int) = &max;
	int a = (*fp)(1,2);
	printf("%d\n",a);
	int b = fp(1,2);
	printf("%d\n",b);
	int (*fp2)(int,int) = max;
	int c = (*fp2)(1,2);
	printf("%d\n",c);

	printf("%p,%p\n",max,&max);
	printf("%ld,%ld\n",sizeof(max),sizeof(&max));
	return 0;
}

int max(int x,int y){
	return x>y?x:y;
}

 在程序中定义了 int (*fp)(int,int),

含义是:定义了一个指针变量fp,该指针变量可以指向返回值为int型,且有两个整型参数的函数,

fp的类型为 int(*)(int,int).

对函数指针变量赋值后,就可以通过解引用的方式调用fp所指向的函数了。

ANSI C标准运行我们将将(*fp)(1,2)简写成fp(1,2),但要明白这种写法只是一种简写形式。

另外,对指针变量进行赋值,可以直接使用函数名,也可以&函数名,因为地址值是相同的,怎么解释这个地址只看函数指针变量的类型。就像在上个部分数组与指针,把不同类型的地址赋值给数组指针,程序运行结果并不会有不同,只是编译会有警告提示。而在函数指针这里,并不会有警告。尽管max函数名是int(int,int)类型,而&max才是int(*)(int,int)类型。

(3)指针函数

指针函数就比较简单了,就是返回值是一个地址类型的函数

#include <stdio.h>

int *query(int *arr,int n);
int main(void)
{
	static int arr_score[10] = {87,89,85,76,65,70,72,85,97,99};
	for(int i = 0 ;i < 10; i++)
		printf("%d\t", *query(arr_score,i));
	printf("\n");

	int *(*fp)(int *arr, int n) = &query;
	for(int i = 0 ;i < 10; i++)
		printf("%d\t", *(*fp)(arr_score,i));
	printf("\n");
	return 0;
}
int *query(int *arr,int n){
	return arr + n;
} 

 这里对 int *(*fp)(int *arr, int n) = &query 指针函数的指针的定义

和*(*fp)(arr_score,i) 该指针的调用和解引用做一下解释。

首先,()的优先级是最高的,所以(*fp)说明 fp是一个指针变量,然后前面的int *说明表示这个指针变量可以指向返回值为int *的函数;后面括号中的参数就应该不需要说了。

然后 *fp,首先fp是函数指针, *fp就是该指针所指向的函数,由于函数运算符()的优先级高于单目运算符*,所以先给函数传递函数参数,然后对结果进行* 解引用。

3、指针和字符串

在c语言中,并没有字符串这个数据类型。通常借助于字符数组来存储字符串。而字符指针可以存储字符串的起始地址,并且和数组一样,字符数组名就代表了字符串的起始地址。这样,我们就可以用指针来处理字符串。

字符串或者说字符数组和数组在操作上有很多共同之处,但也自己的特殊性。

#include <stdio.h>

int main(void)
{
	char str[] = "hello,world!";
	char *strp = "hello,world";
	printf("%d\n",sizeof(str));
	printf("%d\n",strlen(str));
	int arr[] = {1,2,3,4,5};
	printf("%d\n",sizeof(arr)/4);
	return 0;
}

1、字符串数组默认以‘\0’作为字符串结束标志,所以会自动添加一个‘\0’字符在字符串末尾。

2、注意str 和 strp初始化的区别。详细可以参考(25条消息) C 内存管理(代码区、数据区、堆区、栈区)_熹微seesea的博客-CSDN博客

 3、不能像对数组名取地址操作一样对字符数组名取地址。

#include <stdio.h>

void tocapital(char *str);
int main(void)
{
	char str[] = "hello,world!";
	printf("%s\n",str);
	tocapital(str);
	printf("%s\n",str);
	return 0;
}

void tocapital(char *str){
	while(*str != '\0'){
		if(*str >= 'a' && *str <= 'z') 
			*str -= 32;	
		str ++;
	}
}

4、数组作为函数参数

在c语言中,我们无法将一个数组(包括字符数组)作为函数参数直接传递。如果我们使用数组名作为参数,那么数组名会被转换为指向该数组第一个元素的指针。

例如,在3中的程序使用printf("%s\n",str)和使用printf(”%s\n“,&str[0])完全等效。

在写自定义函数时,将数组作为函数参数毫无意义c语言中会自动地将作为参数的数组声明转换为相应的指针声明,比如使用void tocapital(char str[])和使用void tocapital(char *str)是相同的。

一个常见的例子就是函数main的第二个参数:

int main(int argc,char * argv[]){.....}和

int main(int argc,char **argv){......}是等价的

参考《c陷阱与缺陷》

5、野指针

(25条消息) 野指针和常见的内存错误_熹微seesea的博客-CSDN博客

6、常量指针,指向常量的指针和指向常量的常量指针

(1)int * const p;

p是一个常量类型的指针,一旦初始化就不能修改指针的值,但是这个指针所指向的地址上存储的值可以改变

(2)const int *p;

p是一个指向常量的指针,常量自然不能修改,但是可以改变指针变量的值

(3)const int *const p;

同时满足(1)和(2)的内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值