(C语言)指针详解与应用
指针是C语言的灵魂,指针与底层硬件联系紧密,使用指针可操作数据的地址,实现数据的间接访问。
指针即指针变量,用于存放其他数据单元,如变量、数组、结构体和函数的首地址。若指针存放了某个数据单元的首地址,则这个指针指向了这个数据单元,若指针存放的值为0,则这个指针为空指针。
指针原理
指针占用的内存空间
在32位操作系统下:占用4个字节空间 在64位操作系统下:占用8个字节
int main(){
cout << "sizeof(int *) = " << sizeof(int*) << endl;
cout << "sizeof(char *) = " << sizeof(char*) << endl;
cout << "sizeof(double *) = " << sizeof(double*) << endl;
cout << "sizeof(float *) = " << sizeof(float*) << endl;
return 0;
}结果:我是64位操作系统,所以4个输出都为8,
如何定义指针?
char* p、short* p、int* p等,其中*号只是一个标识,就是告诉计算机我要定义一个指针了,它与数据类型是一个整体,其中p为指针变量,存放的是数据单元的首地址。
指针的操作
int *p;//定义一个指针
内存分配
int *arr = (int*)malloc(10 * sizeof(int)); // 分配10个int空间
free(arr);//释放内存,与malloc是一对
arr = NULL;//防止指针悬空
则对指针p有如下的操作方式
p = &a;//取地址,将数据a的首地址赋值给p
int b =*p;//取内容,即将p指向的地址里的数据给b,就是将a的值赋给b,数据类型要一致
char *p = &a;//意思就是我定义一个指针p并存放a的首地址
p++;//使指针向下移动一个数据宽度,++p同理
p--;//使指针向上移动一个数据宽度,--p同理,++p和--p,错用可能导致越界,一般用于数组
关于*号的区分:
- 若是在数据类型的后面,如int *p,*号就是一个标识符,告诉计算机我要定义指针,无运算
- 若是a = *p,那么这里的*号就是取内容
- 若是a*b,那么这里的*就是乘号
空指针和野指针
空指针
1、指针变量指向内存中编号为0的空间
2、用途:初始化指针变量
注:空指针指向的内存不可以直接访问
3、得到空指针的方式:
int *ptr = nullptr;//最直接,最简单
int *ptr = 0;
int *ptr = NULL;//#include cstdlib
注意:一定要初始化所有的指针。未初始化的指针是造成运行错误的一大原因,访问未被初始化的指针相当于去访问一个本不存在的位置上的本不存在的对象。如果指针所指向的空间恰好有内容,我们就很难分清它到底是合法的还是非法的了。
所以建议初始化所有的指针,尽量定义好对象之后在定义指向它的指针。实在不清楚的情况下,就把指针初始化为nullptr。
野指针
指针变量指向非法的内存空间
出现的原因:
1、指针变量未初始化
2、指针释放后之后未置空
3、指针操作超越变量作用域
int main()
{
int *p = (int*)0x1100;//0x1100是随便指向的编号,而编号里的数无权操作,因为未申请空间
cout << *p << endl;
return 0;
}
在程序中要避免出现野指针,十分危险。
总结:空指针和野指针都不是我们申请的空间,所以不要访问。
const修饰指针
const修饰指针有三种情况:
1、const修饰指针:常量指针
特点:指针的指向可以修改,但是指针指向的值不可以修改
const int *p = &a;
*p = 20;//错误,指针指向的值不可以修改
p = &b; //正确,指针的指向可以修改
2、const修饰常量:指针常量
特点:指针的指向不可以改,但是指针指向的值可以改
int * const p = &a;
*p = 20;//正确,指针指向的值可以修改
p = &b;//错误,指针的指向不可以修改
3、const既修饰指针,也修饰常量
特点:指针的指向和指针指向的值都不可以改
const int * const p = &a;
*p = 20;//错误
p = &b;//错误
记忆技巧:
1、名称记忆:将const翻译为中文”常量“,” * “就叫指针,const在前,*在后,就叫常量指针, const在后,*在前,就叫指针常量。
2、可否修改记忆:要明白const是起限定的作用,int *const p中的const限定的是指针p,而指针的是起到指向的作用,所以p就不能指向了,也就不能修改指向了;const int *p中的const限定的是*,而*的作用是取值,所以*就不能取值了,也就不能修改值了。
数组与指针
数组是一些相同数据类型的变量组成的集合,其数组名即为指向该数组类型的指针,简单理解就是数组名相当于指针变量,里面存放的都是首地址。
c[0] 等于 *c;
c[1] 等于 *(c+1)
c[2] 等于 *(c+2)
遍历的方法:
1、用for循环遍历
int *p = arr; //arr是一个数组名
for(int i = 0;i < 10;i ++)
{cout << p[i] << endl;
}2、用指针遍历
for(int i = 0;i < 10;i ++)
{
cout << *arr << endl;
arr ++;
}
指针与函数
本节主要讨论的是函数的参数采用指针的形式,又叫地址传递。
void swap(int *a,int *b) //地址传递
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a = 10;
int b = 20;
swap(&a,&b);
cout << "a = " << a << " b = " << b << endl;
return 0;}
这是一个简单的交换值的程序,这段代码改变了实参a和b的值
总结:如果不想修改实参,就用值传递;想修改实参,就用地址传递。
函数指针与指针函数
| 主体(是指针还是函数) | 需求 | |
| 指针函数 | 函数 | 返回类型为指针 |
| 函数指针 | 指针 | 指向一个函数 |
首先要明白()的优先级大于 *,主要看f
int* f(int x,int y): 因为()的优先级大于 *,所以首先主体是一个函数,返回指针
int (*f)(int x,int y): 首先f是一个指针,指向一个函数格式为int (int x,int y)
函数指针指向普通函数
#include <iostream>
using namespace std;int add(int a,int b)
{
return a+b;
}int Minus(int a,int b)
{
return a-b;
}int main()
{
//定义一个函数指针
int (*f)(int a,int b);
f = &add;//将函数add的地址给函数指针
cout << "a + b = " << (*f )(1,3)<< endl;
f = &Minus;//将函数minus的地址给函数指针
cout << "a - b = " << (*f)(5,6) << endl;
return 0;
}
函数指针指向指针函数
#include <iostream>
using namespace std;int* add(int a,int b) //指针函数
{
int c = a+b;
return &c;
}int* Minus(int a,int b) //指针函数
{
int c = a-b;
return &c;
}int main()
{
//定义一个函数指针
int* (*f)(int a,int b);
f = add;//将函数add的地址给函数指针
cout << "a + b = " << *(*f )(1,3)<< endl;
f = Minus;//将函数minus的地址给函数指针
cout << "a - b = " << *(*f)(5,6) << endl;
return 0;
}
总结:
1、指针函数依然是一个函数,返回类型为指针,比如int*、char*等
2、而函数指针是一个指针,它指向函数,存放函数的地址(函数地址与返回值地址一致)
3、函数指针的格式与要调用的函数格式保持一致
注意事项
- 在对指针取内容之前,一定要确保指针指在了合法的位置,否则将会导致程序出现不可预知的错误
- 同级指针之间才能相互赋值,跨级赋值会导致报错或警告
指针的应用
传递参数
又叫地址传递
1、使用指针传递大容量的参数,主函数和子函数使用的是同一套数据,避免了参数传递过程中的数据复制,提高了运行效率,减少了内存使用。
#include <stdio.h>
int FindMax(int *array,int count)//子函数,找最大值的简单算法
{
int i;
int max = array[0];
for(i = 1;i < count;i++)
{
if(array[i] > max)
{
max = array[i];
}
}
return max;
}
int main(){
int a[] = {1,5,7,2,3,4,15};
int Max;
Max = FindMax(a,7);
printf("Max = %d",Max);
}
以上是一个找最大值的简单算法,其中数组a的容量比较大,可以通过指针传地址的方式减小空间的使用。若采用传地址的方式,那么数据就可以在子函数里被改变,而不是简单的复制。所以为了不让子函数随意的更改主函数的数据,我们可以用const关键字。
int FindMax(constint *array,int count) //const的运用
{
array[2] = 66;//若这样写,就会报警告或是报错,不可以更改数组a里的数据,只可以读
}
2、使用指针传递输出参数,利用主函数和子函数使用同一套数据的特性,实现数据等返回,可实现多返回值函数的设计,运用了地址传递。
一般情况下,C语言只能返回一个参数,那如果我们想要返回多个参数怎么办呢?
#include <stdio.h>
//子函数可以返回次数count与最大值max的值,可以不需要return
int FindMax(int *max, int *count, const int *array, int length)
{
*max = array[0];
for(int i = 1;i < length;i ++)
{
if(*max < array[i])
{
*max = array[i];
++(*count);
}
}
}int main(){
int a[] = {1,5,7,2,3,4,15};
int Max;
int Count = 0;
FindMax(&Max,&Count,a,7);//将Max地址与Count的地址都传递给子函数
printf("Max = %d,Count = %d",Max,Count);
}
这样我们就可以返回多个参数了。
传递返回值
将模块内的共有部分返回,让主函数持有模块的“句柄”,便于程序对指定对象的操作。
#include <stdio.h>
/*******************************************/
//假设这是一个模块,里面的Time数据我们不能直接访问,所以通过指针来进行间接访问
int Time[] = {23,50,55};//注意:若是局部变量则出错
int *GetTime(void)
{
return Time;
}
/*******************************************/
//以上为一个模块
int main(){
int *pt;
pt = GetTime();
printf("pt[0] = %d\n",pt[0]);//pt[0] = 23
}
直接访问物理地址下的数据
访问硬件指定内存下的数据,如设备ID号等。
将复杂格式的数据转换为字节,方便通信与存储。
