【C语言】指针的相关内容

指针的使用,方便了内存的管理,既然要了解指针的相关内容,首先就要了解一下指针是什么?再来了解相关内容。

指针是什么?

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。

假设定义了一个int *p类型的指针变量。

如图所示的指针对应到相应的代码如下所示:

#include<stdio.h>
int main()
{
	int a = 10;//在内存中开辟一块空间
	int *p = &a;//对变量a,取出它的地址,可以使用&操作符
				//将a的地址存放在p变量中,p就是一个指针变量
	return 0;
}
  • 总体来说,指针就是一个用来存放地址的指针变量。
  • 存放在指针中的值都会被当作地址来处理。
  • 指针让地址有地方存放,指针让内存的访问更加方便。
  • 值得注意的是,指针的大小是固定的,32位机器为4个字节的空间,64位机器为8个字节的空间。

相关内容

  • 指针数组
  • 数组指针
  • 指针和数组的定义与声明
  • 数组参数与指针参数
  • 函数指针
  • 函数指针数组、指向函数指针数组的指针
  • 回调函数

指针数组

指针数组,可以理解为指针的数组,就是一个存放指针的数组,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。

先看几个定义:

char *p1 = "abcdef";
char *p2[2] = {"hello","world"};

这里是创建了一个数组,数组的首字符地址存放在p1这个指针变量里。至于这个p1这个指针变量的地址在哪里存,怎么存,就先不深究了。

这里创建了一个指针数组,p2为数组名,这个指针数组里存放的是“hello”,“world”这两个字符串首字符的地址。p2 先与“[ ]”结合,构成一个数组的定义,数组名为p2,char *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含2个指向char 类型数据的指针,即指针数组。那么如何把这两个字符串打印出来?写出以下代码:

#include<stdio.h>
int main()
{
	char *p2[2] = { "hello","world" };
	int i = 0;
	for (i = 0; i < sizeof(p2) / sizeof(p2[0]); i++)
	{
		printf("%s\n", p2[i]);
	}
	return 0;
}

数组指针

数组指针,可以理解为数组的指针,就是一个指针,存放数组的地址,指向数组。在32 位系统下永远是占4个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。

举个例子:

int *p[10];
int (*p)[10];

在这里的两个p分别是什么?
答案:指针。
第一个是指针数组;
第二个是数组指针。

 int  (*p)[10]; 

由于[ ]的优先级高于* ,所以加上( )来保证p和*结合。
在这里“( )”的优先级比“[ ]”高,“ * ”号和p 构成一个指针的定义,指针变量名为p,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。

数组指针的使用:

int arr[10] = { 0 };
int (*p)[10] = &arr;

p是一个数组指针,所以存放数组的地址是合适的。

既然这样,就想到这样来使用数组指针:

#include <stdio.h>
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	int(*p)[10] = &arr;
	int i = 0;
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", (*p)[i]);
	}
}

这样使用的话,要打印一个数组也有点太麻烦了。不如直接使用下标来打印数组。可见,数组指针不是这样使用的。

下面来看看它的真正用法。

一般用法,二维以上的数组使用它。

普通打印二维数组:

#include <stdio.h>
void print(int arr[3][5], int x,int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1 ,2 ,3 ,4 ,5 ,5 ,4 ,3 ,2 ,1 ,6 ,7 ,8 ,9 ,0 };
	print(arr, 3,5);
	return 0;
}

数组指针打印二维数组:

#include <stdio.h>
void print(int (*p)[5], int x,int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d", *(*(p+i)+j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1 ,2 ,3 ,4 ,5 ,5 ,4 ,3 ,2 ,1 ,6 ,7 ,8 ,9 ,0 };
	print(arr, 3,5);
	return 0;
}

在这里,把二维数组通过数组指针降了一维,使得数组指针的用法比普通用法效率高一点。

小练习

了解指针数组数组指针之后来看看下面代码都是什么意思:

int arr[5];
//定义了一个整型数组
int *parr1[10];
//定义了一个指针数组
int (*parr2)[10];
//定义了一个数组指针,parr2不能指向parr1,类型差异
int (*parr3[10])[5];
//parr3为一个数组,里面有10个元素,存放的是数组指针,指针指向一个数组,每个数组里面有5个元素,5个元素的类型为int型

##指针和数组的定义与声明
定义是不存在的时候要让他存在,而声明是不知道的东西让他被知道。
先来看一组代码,输出结果是什么?

//main.c
//数组的定义
char arr[] = "abcdef";
//指针的定义
char *p = "abcdef";


//test.c
extern char arr[];//声明
extern char *p;//声明
int main()
{
	printf("%s\n", arr);
	printf("%s\n", p);
	return 0;
}


在test.c中的extern表示arr和p是外部文件定义的变量,在使用的时候去其他模块查找。也可以理解为声明了一个外部变量。通过结果可以分析到:声明的其实就是定义的变量本身,声明使用的空间就是定义的那块空间。

了解了声明与定义的正确用法,通过下面的1个例子来深入了解一下:

//main.c
//指针的定义
char *p = "abcdef";

//test.c
extern char p[];//声明
int main()
{
	printf("%s\n", p);
	return 0;
}

上面的程序输出结果如下:


数组和指针是不一样的。数组不是指针,指针不是数组。

数组参数与指针参数

在写代码的时候难免要把数组或者指针传给参数,那函数的参数是如何设计的?我们都知道参数分为形参和实参。形参是指声明或定义函数时的参数,而实参是在调用函数时主调函数传递过来的实际值。

一维数组传参

#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr[20])
{}
void teat2(int **arr)
{}
int main()
{
	int arr[10] = { 0 };
	int *arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

上面一维数组传参的方式都是可以的。

二维数组传参

void test(int arr[3][5])
{}
void test(int arr[][])//X
{}
void test(int arr[][5])
{}
void test(int *arr)//X
{}
void test(int* arr[5])//X
{}
void test(int(*arr)[5])
{}
void test(int **arr)//X
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

上面注释为X的都是不可以传参的方式。

二维数组传参,函数形参的设计只能忽略第一个[ ]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行有多少个元素。这样才方便运算。

一级指针传参

举个例子

void test1 (int *p)
{}

test1函数能接受什么参数?
1.整型指针变量的地址
2.整型指针变量本身
3.整形数组

void test2(char *p)
{}

test2函数能接受什么参数?
1.字符指针变量的地址
2.字符指针变量本身
3.字符数组

二级指针传参

#include <stdio.h>
void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int*p = &n;
	int **pp = &p;
	test(pp);
	test(&p);
	return 0;
}

当函数的参数为二级指针的时候 ,可以接受什么参数?
1.一级指针变量的地址
2.char *指针数组
3.二级指针变量本身

函数指针

函数指针,就是函数的指针,它是一个指针,指向一个函数。

&函数名和函数名都是函数的地址,因为函数名被编译之后就是一个地址。

#include <stdio.h>
#include <string.h>
char * fun(char * p1, char * p2)
{
	int i = 0;
	i = strcmp(p1, p2);
	if (0 == i)
	{
		return p1;
	}
	else
	{
		return p2;
	}
}
int main()
{
	char * (*pf)(char * p1, char * p2);//定义了一个函数指针
	pf = &fun;
	(*pf) ("aa", "bb");//通过函数指针调用函数
	return 0;
}

使用函数指针的好处在于,可以将实现同一功能的多个模块统一起来标识,这样一来更容易后期的维护,系统结构更加清晰。

小练习

(*(void(*)())0)();

调用0地址处的函数,该函数无参,无类型。
1.void()()这是一个函数指针类型
2.(void(
)())0这是将0 强制转换为函数指针类型,0 是一个地址,也就是一个函数存在首地址为0 的一段区域内
3.(* (void( * )())0)这是取0 地址开始的一段内存里面的内容,其内容就是保存在首地址为0 的一段区域内的函数。
4.( * (void( * )())0)()调用0地址处的函数,该函数无参,无类型。

void(*signal(int, void(*)(int)))(int);

1.signal是一次函数声明。
2.signal函数的参数,第一个是int,第二个是函数指针。该函数指针指向的函数参数为int,返回类型为int。
3.signal函数的返回类型为一个函数指针,该函数指针指向的函数参数为int,返回类型为void。
4.可以简化为以下代码。

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

函数指针数组、指向函数指针数组的指针

函数指针数组

函数指针数组,顾名思义就是函数指针的数组,这是一个数组,数组的内容为函数的指针。

前面了解到“ char * (* pf)(char * p1, char * p2) ”是定义了一个函数指针pf,那么这个函数指针就可以存在一个数组里,这就是函数指针数组。

char * (* pf[10])(char * p1, char * p2)
pf先于[ ]结合,说明pf是数组。
数组的内容是char * (* )(char * p1, char * p2) 类型的函数指针。

函数指针数组的用途:转移表

转移表:简单计算器

指向函数指针数组的指针

指向函数指针数组的指针是一个指针,这个指针指向一个数组,数组的元素为函数的指针
定义举例:

void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	//函数指针pfun
	void(*pfun)(const char*) = test;
	//函数指针的数组pfunArr
	void(*pfunArr[5])(const char* str);
	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void(*(*ppfunArr)[10])(const char*) = &pfunArr;
	return 0;
}

回调函数

回调函数就是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另外一个函数,当这个指针被用来调用其所指向的函数时,就说这是一个回调函数。回调函数不是由该函数的实现方法直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

使用回调函数,模拟实现qsort(采用冒泡的方式)。

模拟实现qsort

相关练习题

练习1
该程序的结果是什么?

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

练习2

struct Test
{
	int Num;
	char *pcName;
	shortsDate;
	char cha[2];
	shortsBa[4];
}*p;
假设p的值为0x100000。如下表达式的值分别为多少?
p + 0x1 = 0x___ ?
(unsigned long)p + 0x1 = 0x___ ?
(unsigned int*)p + 0x1 = 0x___ ?

练习3

int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;

练习4

{
#include <stdio.h>
int main(int argc, char * argv[])
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int *p;
	p = a[0];
	printf( "%d", p[0]);
}

练习5

int main()
{
 int a[5][5];
 int(*p)[4];
 p = a;
 printf( "a_ptr=%#p,p_ptr=%#p\n" , &a[4][2], &p[4][2]);
 printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
 return 0;
}

练习6

#include <stdio.h>
int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int *ptr1 = (int *)(&aa + 1);
	int *ptr2 = (int *)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}

练习7

#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0;
}

练习8

int main()
{
 char *c[] = {"ENTER","NEW","POINT","FIRST"};
 char**cp[] = {c+3,c+2,c+1,c};
 char***cpp = cp;
 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 return 0;
}

如有不足之处,欢迎指正!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值