说起指针,网上总是有很多的文章,各种由浅入深,再深的都有;且各种语言的视频讲解也有很多。本文并不是要说写的有多好,能让人一看就懂,这是不现实的;因为指针涉及了内存操作,如果不理解操作系统,不理解内存分配,讲的再多也如无根之莲,漂泊于水面。故本文只能算是一个借鉴和参考 ,顺便整理一下自己的思路,不至于每次看到指针,都要去百度搜索一遍。
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;
到此,有关指针的内容基本介绍完了,里面还有很多细节没有进行详细的展开,且在实际项目中用法也是更加千奇百怪,这个只能慢慢进行积累。