一、如何理解指针?
不论是什么语言,当变量声明之后,电脑将分配一块内存给该变量进行存储,那么学过计算机组成原理的话我们就可以知道,当电脑需要调用该变量时,是通过CPU的寄存器里面保存的地址来进行“寻址”操作。
我们可以做一个简单的类比,将内存看作一个庞大的连续别墅群,每一个别墅都有一个特定的编号。假设每一个别墅存储的信息量为一个Byte,别墅群能够存储的总信息量为4GB(2^32Bytes),那么我们就可以按照这个标准对每一栋别墅进行编号,从0x00000001到0xFFFFFFFF(也就是十进制的1~4294967296)。
![](https://img-blog.csdnimg.cn/img_convert/aa7bc73e31d2a680ea13af01ffc331f4.jpeg)
CPU可以看作这一整个别墅群的运作管理中心,目前的CPU大部分都是以4Bytes(32bits)为一个存储单位,可以类比为,一个家庭拥有4栋别墅来进行工作。但是呢,在我们的C语言中,并不是所有变量都只有32bits(int和char类型的数据是32bit),也有一些变量需要多于4Bytes。
由于我们需要保证数据的连续性,所以一个数据大概率存储在相邻的“别墅”中,所以当我们需要找到某部分特定数据,就需要通过地址信息找到第一栋别墅然后按照顺序去读取“别墅中存储的信息。
在C语言中,这个地址信息就被称作指针 !用来找到对应的数据块。
由于不同的数据类型有不同的编码解码方式以及大小, 所以当我们声明指针时,也需要明确指针指向的数据类型。
//int类型的指针
int* int_point;
//声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针
int* arr[10]
// 声明一个数组指针,该指针指向一个 int 类型的一维数组
int (*arr)[10]
// 声明一个指针 p ,该指针指向一个 int 类型的指针
int **p;
//char类型的指针
char * char_point;
//结构类型的指针
struct name * struct_point;
在声明时加入“*”运算符表示该变量存储的是指针,也就是地址。
这里需要强调几个点:
(1)<int* arr[10]>: arr里面存储的都是指针,每一个指针都指向一个int类型的数据。
<int (*arr)[10]>: arr这个变量存储的是某个长度为10的数组的指针,也就是数组第一个元素的地址。
(2)<int **p>: 指针同样也可以被看作是一个存储在内存中的变量,因此同样也可以为他们提供指针进行寻找。
(3)如果p是一个指针,保存一个地址,该地址指向内存中的某个变量,“*p”可以访问该指针对应的变量。
二、指针的初始化
/* 方法1:使指针指向现有的内存 */
int x = 1;
int *p = &x;
// 指针 p 被初始化,指向变量 x ,其中取地址符 & 用于产生操作数内存地址
/* 方法2:动态分配内存给指针 */
int *p;
p = (int *)malloc(sizeof(int) * 10);
// malloc 函数用于动态分配内存
free(p);
// free 函数用于释放一块已经分配的内存,常与 malloc 函数一起使用,要使用这两个函数需要头文件 stdlib.h
指针的初始化便是为指针赋值一个合法的地址,让程序知道这个指针指向哪里。
三、未初始化和非法的指针
在C语言的编辑过程中,我们经常会遇见“ Segmentation fault(core dumped)”
这个原因大部分是因为非法指针的存在,举个例子:
#include "stdio.h"
int main(){
int *p;
*p = 1;
printf("%d\n",*p);
return 0;
}
这个程序编译能通过,但是运行会报错,因为指针p并没有指向任何存储变量的地址。
需要进行如下更改:
#include "stdio.h"
int main(){
int x = 1;
int *p = &x;
printf("%d\n",*p);
*p = 2;
printf("%d\n",*p);
return 0;
}
四、NULL指针
NULL指针表示不指向任何对象。
#include "stdio.h"
int main(){
int *p = NULL;
printf("p的地址为%p\n",p);
return 0;
}
/***************
* 程序输出:
* p的地址为(nil)
***************/
五、指针的运算
指针+/-整数
p++指向的是p的下一个内存地址。这种运算并不会改变p自己的地址,而仅仅是改变指向的地址。
指针-指针
只有当指针指向同一个数组里的元素时才能进行这样的运算,计算出来的结果是两个指针之间的操作数的数量。
#include "stdio.h"
int main(){
int a[10] = {1,2,3,4,5,6,7,8,9,0};
int sub;
int *p1 = &a[2];
int *p2 = &a[8];
sub = p2-p1;
printf("%d\n",sub); // 输出结果为 6
return 0;
}
六、指针与数组
在C语言中,通过指针对数组进行操作极大地提高了数组操作的效率,相较于Java等面向对象等语言,更加直接。
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
//arr是该数组第一个元素的地址
int *p;
//两种给指针赋值的方式:
//1.&是得到变量地址的运算符
p = &arr[0];
//2.直接用arr进行赋值
p = arr;
数组指针的操作:
#include "stdio.h"
int main(){
int arr[2][3] = {1,2,3,4,5,6}; // 定义一个二维数组并初始化
int (*p)[3]; // 定义一个数组指针,指针指向一个含有3个元素的一维数组
p = arr; // 将二维数组的首地址赋给 p,此时 p 指向 arr[0] 或 &arr[0][0]
printf("%d\n",(*p)[0]); // 输出结果为 1
p++; // 对 p 进行算术运算,此时 p 将指向二维数组的下一行的首地址,即 &arr[1][0]
printf("%d\n",(*p)[1]); // 输出结果为5
return 0;
}
七、指针与结构
结构的两种声明方式:
struct message{ // 声明一个结构 message
char name[10]; // 成员
int age;
int score;
};
typedef struct message s_message; // 类型定义符 typedef
s_message mess = {"tongye",23,83}; // 声明一个 struct message 类型的变量 mess,并对其进行初始化
--------------------------------------------------------------------------------------------------------------
/* 另一种更简便的声明方法 */
typedef struct{
char name[10];
int age;
int score;
}message;
对上面的结构可以定义结构指针,并通过结构指针来操作结构内容
s_message *p; // 声明一个结构指针 p ,该指针指向一个 s_message 类型的结构
*p = &mess; // 对结构指针的初始化与普通指针一样,也是使用取地址符 &
#include "stdio.h"
typedef struct{
char name[10];
int age;
int score;
}message;
int main(){
message mess = {"tongye",23,83};
message *p = &mess;
printf("%s\n",p->name); // 输出结果为:tongye
printf("%d\n",p->score); // 输出结果为:83
return 0;
}
八、指针与函数
C语言的所有参数均是以“传值调用”的方式进行传递的,这意味着函数将获得参数值的一份拷贝。这样,函数可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数。
指针可以作为函数的参数进行传递,相较于直接传递变量,这种方式的好处是,不需要对传递的变量进行更改,而仅仅只需要最指针所对应的内存空间的值进行更改,无需更改指针。
#include "stdio.h"
void swap1(int a,int b) // 参数为普通的 int 变量
{
int temp;
temp = a;
a = b;
b = temp;
}
void swap2(int *a,int *b) // 参数为指针,接受调用函数传递过来的变量地址作为参数,对所指地址处的内容进行操作
{
int temp; // 最终结果是,地址本身并没有改变,但是这一地址所对应的内存段中的内容发生了变化,即x,y的值发生了变化
temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int x = 1,y = 2;
swap1(x,y); // 将 x,y 的值本身作为参数传递给了被调函数
printf("%d %5d\n",x,y); // 输出结果为:1 2
swap(&x,&y); // 将 x,y 的地址作为参数传递给了被调函数,传递过去的也是一个值,与传值调用不冲突
printf("%d %5d\n",x,y); // 输出结果为:2 1
return 0;
}
然后呢,可以给函数添加一个指针,该指针指向函数的入口地址,这样的指针和其他指针的操作并无二异。
#include "stdio.h"
#include "string.h"
int str_comp(const char *m,const char *n); // 声明一个函数 str_comp,该函数有两个 const char 类型的指针,函数的返回值为 int 类型
void comp(char *a,char *b,int (*prr)(const char *,const char*)); // 声明一个函数 comp ,注意该函数的第三个参数,是一个函数指针
int main()
{
char str1[20]; // 声明一个字符数组
char str2[20];
int (*p)(const char *,const char *) = str_comp; // 声明并初始化一个函数指针,该指针所指向的函数有两个 const char 类型的指针,且返回值为 int 类型
gets(str1); // 使用 gets() 函数从 I/O 读取一行字符串
gets(str2);
comp(str1,str2,p); // 函数指针 p 作为参数传给 comp 函数
return 0;
}
int str_comp(const char *m,const char *n)
{
// 库函数 strcmp 用于比较两个字符串,其原型是: int strcmp(const char *s1,const char *s2);
if(strcmp(m,n) == 0)
return 0;
else
return 1;
}
/* 函数 comp 接受一个函数指针作为它的第三个参数 */
void comp(char *a,char *b,int (*prr)(const char *,const char*))
{
if((*prr)(a,b) == 0)
printf("str1 = str2\n");
else
printf("str1 != str2\n");
}
“(*p)”中的括号不能省略,否则将表示p为函数,返回一个类型为“int*”的值。
本文鸣谢博客:
代码来源于上述博客,转载或使用请说明