1. 指针的定义

指针:是在许多编程语言中用来存储内存地址的变量。指针变量的值直接指向(points to)存在该地址的对象的值。所指向的可以是计算机内存中的另一个值,或者在某些情况下,是内存映射计算机硬件的值。【来源-维基百科】

指针定义方式:数据类型 *指针变量

下面是有关的符号

*:指针标记(间接访问操作符);

&:地址运算符;

//exapmle
int num = 10;
int *p = #

2.指针的类型和操作

指针类型

指针类型就是该指针所指向的==内存中保存的数据类型==

下面是类型的定义:

例如整型指针就为int *p;

字符指针就为char *c;

指针的大小:指针的大小在32位平台式4个字节,在64位平台式8个字节

指针操作

指针解引用*

可以读取或修改指针所指向的内存地址里的变量 / 数据值

指针+-整数

代表指针在内存中前后移动一定数量的元素单元

int类型的指针就移动4字节,char类型的指针移动1字节,取决于类型大小

下面是一个结构体的案例

struct test{
	char c;
	int a;
};	//此结构体为8字节 大小取决于最大类型

int main()
{
	struct test n1;
	struct test* p = &n1;
	printf("结构体大小%d\n", sizeof(n1));	// 结构体大小8
	//将结构体地址的指针++,目前指向n1的位置
	printf("结构体指针地址%p\n", p);		// 结构体指针地址0061FEA0
	p++;
	printf("结构体指针地址%p\n", p);		// 结构体指针地址0061FEA8
	return 0;
}

[!Note]

注意野指针问题,未初始化的指针就是野指针,也可以是指向未初始化的地址。

如何规避野指针:

  • 指针初始化
  • 小心指针越界
  • 指针指向空间释放,即置为空NULL

3.指针和数组

指针与数组的关系:

数组名 = 数组首元素地址 = 指针 = 数组第一个字节地址,下面的代码==p等价于arr[0]==

int arr[] = { 1, 2 };
int *p = arr;

通过指针访问数组元素可以用使用[],也可以使用*

example:p代用上面的指针,这里可以访问数组第二个元素

p[1]或者*(p+1)

4.指针运算

指针相减运算p1-p2

  • 计算两个指针之间相差的偏移量或元素个数
  • 偏移量表示两个指针所指向内存地址之间的距离,(即两个指针指向的地址之间的内存区域大小)

指针比较运算
使用关系元素符< > <= >= == !=来比较指针,反映指针所指向地址的前后关系

5.二级指针

即指向指针的指针(保存指针的地址)

example:

int num = 10;
int *ptr = &num;
int **pptr = &ptr;
printf("%d", **(pptr))

可以通过二级指针访问一级指针然后访问变量值

6.字符指针

即字符类型的指针

char str[] = "hello world";
char *p = str;
char c = 'a';
char *p2 = &c;

常量指针

指针指向的字符串为常量,不可修改,以下两种都不可修改

const char *p1 = "hello world";
char *p2 = "hello world";
const char *p3 = "hello world";

常量指针注意的是==当有几个指针定义为同样的常量时==,==都指向同一块地址==,上面代码p1和p2还有p3指向的都是同一个地址,这是由于c/c++会把字符串常量存储到内存的常量区,并且常量区的数据也是程序运行结束后系统才会释放。

但是字符数组定义的字符串若是局部变量则开辟在栈区,并指向不同的内存块地址

常量指针和指针常量

常量指针:指向的内容不可以通过该指针改变,但是指针本身可以指向其他地址,用别的指针指向这块内存时仍能修改其值

const char * ptr

指针常量:指针本身只能指向一个地址,不能指向其他地址,但指向的内容能够通过该指针修改

char * const ptr

7.指针数组

存放指针的数组,下面是一级指针数组和二级指针数组

example:

int *ptr[10];			//	存放10个int类型指针的数组
int *pptr = ptr;		//	存放上面10个int类型指针的数组的地址
int num = 10;
ptr[0] = &num;
//此时可以通过二级指针来解引用一级指针所指向的地址的值
//当pptr解引用时还是一个指针,所以需要再解引用
printf("%d", **(pptr))

数组指针:指向整个数组的指针,而不是指向某一单个元素

其定义形式:数据类型 (*指针名)[数组长度]

下面是其定义的例子

int arr[5] = {1, 2, 3, 4, 5};
int (*p)[5] = &arr;		// 指向包含5个整型元素的数组的地址

[!note]

但是函数形参以此方式接收会退化为指向单一元素地址

8.函数指针

函数指针的定义:

根据函数的返回值,参数来定义

返回值类型 (*函数指针变量名) (参数类型列表) = 函数名/函数地址

example:

void fun(int num1, int num2);
void (*p)(int, int) = fun;

函数在内存的位置为代码区(也可以叫文本区)程序指令,以及函数体都存放在此,但是运行时是在栈区运行

函数指针的使用

下面是一个使用函数指针来调用函数的例子

example:

void fun(num1, num2)
{
	printf("%d\n", num1 + num2);
}
int main()
{
	void (*p)(int, int) = fun;
	int num1 = 10, num2 = 20;
	p(num1, num2);
}

指针函数

即返回值为指针的函数

下面的例子为返回全局变量的地址

int num = 1;
int* func()
{
    num = 2;
    return &num;	//返回全局变量地址
}
int main()
{
    int *p = func();
    printf("%d", *p);
}

9.回调函数

回调函数的声明

格式:typedef 返回值类型 (*回调函数类型)(参数列表)

即先需要声明类型,然后才能在函数内使用函数作为形参

下面写一个计算两个整型的和的函数并调用自己写的打印函数。

example:

//定义回调函数类型
typedef void (*PrintFunc)(int num);
//声明回调函数
void myPrint(int num)
{
	printf("%d", num);
}
//函数参数可以随意取函数名,都是调用传进来的回调函数
void sumNum(PrintFunc printNum, int num1, int num2)
{
	int sum = num1 + num2;
	//调用回调函数打印
	printNum(sum);
}

int main()
{
	int num1 = 10;
	int num2 = 20;
	sumNum(myPrint, num1, num2);
	return 0;
}