C语言指针详解

说起指针,网上总是有很多的文章,各种由浅入深,再深的都有;且各种语言的视频讲解也有很多。本文并不是要说写的有多好,能让人一看就懂,这是不现实的;因为指针涉及了内存操作,如果不理解操作系统,不理解内存分配,讲的再多也如无根之莲,漂泊于水面。故本文只能算是一个借鉴和参考 ,顺便整理一下自己的思路,不至于每次看到指针,都要去百度搜索一遍。

1.指针是什么?

答:指针就是一个值为内存地址的变量。

2.内存地址是什么?

答:内存地址就是。。。。

我们知道计算机有个内存条,有4G大小的,有8G大小,有16G大小。内存条是物理概念,而内存就是抽象概念,对于内存条的抽象,把它理解为一块同样的大小的一个空间即可,比如占地10亩,100亩,就是这样直接简单的概念。

3.指针和内存地址的联系?

答:指针 = 内存地址(4Byte)

4.为什么是4Byte呢?

因为在32位系统中,CPU的总线是32位的,4Byte * 8bit = 32bit,即不管是数据总线,还是地址或者控制总线,最多只能承载4Byte宽度大小的数据。你看,这里又涉及计算机组成原理的知识,所以指针说简单的原因在于 当理解计算机之后,指针确实简单;当你不理解计算机的时候,指针就确实困难。

给一个内存想象图:

 这就是内存和指针的关系,当然,图中的地址和数值是我瞎编的。后面用程序来给出真正的地址值和数值。

下面进入正题:

一、指针的定义及使用

 数据类型有char        int         float,所以指针也会有这些类型,不过它们都是4字节,因为它是个地址,只能占4字节。下面用char 来进行说明。

指针定义:

在数据类型后面加上一个* 运算符,就说明它是个指针了。

注意:指针不初始化的时候,先赋值为NULL,不然很容易指向未知地址。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
	char* a = NULL;
	int b = 0;
	b = sizeof(a);
	printf("指针的大小:%d \n", b);
	printf("指针的地址:%04X \n", a);
	return 0;
}

输出结果:

使用指针,有两种方式:

1.使用 * 号运算符去取对应的值;

 

2.使用 & 号运算符去取对应的地址;

看代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
	char* a = NULL;
	int b = 100;
	a = &b;	//将b的地址赋值给a
	printf("指针的值:0x%04x \n", a);
	printf("指针地址存储的值:%d \n", *a);
	return 0;
}

运行结果:

可以看出,&运算符可以取出变量的地址,*运算符可以取出地址中存储的值。此时内存图如下:

 至于b存在哪里,可以自己尝试输出看一下。

单纯的指针好理解,下面讲点更复杂的

二、指针的分类

1.指针和数组的结合

先定义一个指针和数组,分别看一下它们是什么。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
	char* a = NULL;
	char b[2] = {0};
	int c = 0;
	a = &c;
	printf("指针的值:	%04x \n", a);
	printf("数组的值:	%04x \n", b);

	return 0;
}

可以看到,指针本身是地址,我们可以理解,那么现在数组的名字也是地址。既然都是地址,那么是不是可以相互使用呢?答案是可以的。

其实指针的发明一部分功能上就是为数组服务的。不过指针和数组的结合方式有两种,它是不同的概念:

(1)指针数组

[] 的优先级比 * 高,所以从右往左结合,它先是一个数组,然后变成指针

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
	char* a[2] = { "a", "b" };
	printf("%04x %04x\n", a[0], a[1]);
	return 0;
}

db7bcc 的十进制是:14384076

db7bd0 的十进制是:14384080

可以看到两者相差4个byte,即a[0] + 4  = a[1]。这也验证了它先是一个数组,数组里面存放的是指针。

(2)数组指针

此时 () 的优先级最高,因此,*a 先结合,形成一个指针,然后char 修饰后面都 [] 数组符号。

即它先是一个指针,然后指向了一个数组。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
	char(*a)[10] = {'a'};
	printf("%04x %04x %04x %c", a, a[0], a[1], a[0]);
	return 0;
}

 连续输出a a[0] a[1] a[0]

可以看到,此时 a 和 a[0] 的地址值是相同的,这是数组的标志,数组名地址和数组首元素的地址是相同的,唯一不同的是a++ 和 a[0]++ 的跨越长度不同

因此,数组指针,表示它是一个指向数组的指针。

一般来说,数组指针用做函数参数进行传递

2.指针和函数的结合

(1)指针作为函数参数

目的就是为了解决一个问题:

普通传递是值传递的方式,传入的值是什么样,在函数调用完之后还是什么样;无法做到使用函数调用之后,可以使用修改后的值。

使用指针进行传递,此时传递的是变量的地址,而不是变量的值;注意:不能修改指针变量的值,二是要修改它指向的值

可以传递单个指针,也可以传递数组指针

# include <stdio.h>

void Swap(int* p, int* q);  //函数声明

int main(void)
{
    int i = 3, j = 5;
    Swap(&i, &j);
    printf("i = %d, j = %d\n", i, j);
    return 0;
}

void Swap(int* p, int* q)
{
    int buf;
    buf = *p;
    *p = *q;
    *q = buf;
    return;
}

(2)函数指针

从名字分析,它应该是一个指针,然后指向了一个函数。

() 优先级最高,先结合成一个指针,然后int 修饰 (int a) 函数

和数组指针是一样的原理,数组有地址存放,那么也可以将函数的地址进行存放。

#include <stdio.h>

int test(int a)
{
	return a;
}

int main(int argc, const char* argv[])
{
	int (*fp)(int a);
	fp = test;
	printf("%d ", fp(2));
	return 0;
}

其实就相当于,定义了一个指针去指向这个函数,然后可以使用指针名来调用函数;同时还能作为函数参数传递,此时用处最大,相当于传了一个函数进去。相当于回调函数

最明显的例子,快排:

qsort函数本来就是stdlib库里面的函数,有兴趣的可以看一下源码,感觉挺有意思的

当排序个数少于8时,使用的插入排序;当大于8个时,使用快排;

#include <stdio.h>
#include <stdlib.h>

int int_cmp(const void * p1, const void * p2)
{
	return (*(int*)p1 - *(int*)p2);
}

void _swap(void* p1, void* p2, int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)p1 + i);
		*((char*)p1 + i) = *((char*)p2 + i);
		*((char*)p2 + i) = tmp;
	}
}

//使用冒泡去模拟快排,为了展示指针作为函数参数,函数指针作为函数参数的使用

void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < count - 1; i++)
	{
		for (j = 0; j < count - i - 1; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				_swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

int main(int argc, const char* argv[])
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	int i = 0;

	bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);

	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}

	printf("\n");
	return 0;
}

void *是一种无类型指针,任何类型指针都可以转为void*,它无条件接受各种类型

所以能够作为任何指针参数进行传递。

作用:

1.调用函数

2.做函数参数(回调函数)

(3)指针函数

从名字来看,它肯定是一个函数,然后有一个指针指向了它

int *f(int a, int b)

从左向右结合,f(int a, int b) 先是一个函数,然后有个 int* 的指针指向了函数 f

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int* f(int a, int b)
{
	int* p = (int*)malloc(sizeof(int));
	printf("The memeory address of p = 0x%x \n", p);
	memset(p, 0, sizeof(int));
	*p = a + b;
	printf("*p = %d \n", *p);

	return p;
}

int main(int argc, char* argv[])
{
	int* p1 = NULL;
	printf("The memeory address of p1 = 0x%x \n", p1);

	p1 = f(1, 2);

	printf("The memeory address of p1 = 0x%x \n", p1);
	printf("*p1 = %d \n", *p1);

	getchar();
	return 0;
}

 

一开始地址为空,指针函数 f 的返回值 p 和 f 赋值给的指针 p1 的地址是相同的,都是指向指针函数内部申请的内存地址 0x11a8910。

作用:

如当你需要返回一个数组中的元素时,你就只需返回首元素的地址给调用函数,调用函数即可操作该数组

3.指针和结构体结合

(1)结构体内包含指针

先声明一个结构体:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

struct student {
	int* id;
	char a;
	char* name;
};

int main(int argc, char* argv[])
{
	struct student stu1;
    struct student stu2;
	stu1.id = 1;
	stu1.a = 'A';
	stu1.name = "John";
	
	//读取结构体成员的值
	printf("%d	%c	%s \n", stu1.id, stu1.a, stu1.name);
	return 0;
}

此时都使用 . 符号来进行结构体数据的访问。

结构体变量还可以定义为数组,每个数组成员都是结构体。

加上typedef之后,起个别名,可以更简短的定义结构体变量

(2)结构体指针

1.使用指针来访问变量,同时还能当做函数的参数进行传递

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

typedef struct student {
	int* id;
	char a;
	char* name;
}student;

int main(int argc, char* argv[])
{
	student* stu1;
	stu1->id = 1;
	stu1->a = 'a';
	stu1->name = "aaaa";

	return 0;
}

2.用来构造数据结构

比如链表,二叉树这些数据结构都是由结构体组成的。

typedef struct student {
	int data;
	struct student* next;
}student;

到此,有关指针的内容基本介绍完了,里面还有很多细节没有进行详细的展开,且在实际项目中用法也是更加千奇百怪,这个只能慢慢进行积累。 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值