指针
以下讲解均来自翁惠玉老师出版的书:《第一行代码·C语言》。大家感兴趣的可以去搜索看一下哈!
指针是C语言中的重要概念。所谓指针就是内存的一个地址。有了指针可以使内存访问更加灵活。
在C应用中,指针有多种用途。指针可以增加变量的访问途径,使变量不仅能够通过变量名直接访问,而且可以通过指针间接访问。
指针的用途有两个:1.使程序中的不同部分共享数据;2.在程序执行过程中动态申请空间。
指针的概念
指针与间接访问
内存中每个字节都有一个编号,这个编号称为内存地址。程序运行时每个变量都会有一块运行空间。例如,int类型的变量有一块4个字节的空间。变量的值存放在这块空间中,变量占用这块空间的起始地址称为变量的地址。
当程序访问某个变量时,计算机通过这个对应关系找到变量的地址,访问该地址中的数据。通过变量名访问这块空间中的数据称为直接访问。
这里来个形象的解释来帮助大家理解哈!
学校里A,B,C是三个好朋友,A知道B的家庭地址,所以A可以直接去找B玩(直接访问);A想到C的家去玩,但A不知道C的家庭地址,而B知道C的家庭地址,A询问B关于C的家庭地址,A可以去找C玩啦(间接访问)!
从上例可以看出,所谓的指针变量就是保存另一个变量地址的变量。指针变量存在的意义在于提供直接访问,即通过一个变量访问到另一个变量的值,使变量访问更加灵活。
指针变量的定义
在C语言中,指针变量的定义如下:
类型名 *指针变量名; //比如:int *p;
它表示定义了一个指针变量p,p可以保存一个整型变量的地址,通过p可以间接访问该整型变量的值。
指针指向的变量的类型陈伟指针的基类型。p的基类型为int。
指针变量的操作
指针变量最基本的操作是赋值和引用。
指针变量的赋值就是将某个内存地址保存在该指针变量中。
指针变量的访问有两种方法,
一种是访问指针变量本身的值;
另一种是访问它指向的地址中的内容,即提供间接访问。
指针变量的赋值
为指针变量赋值有两种方法:
- 将本程序的某一变量的地址赋给指针变量。
- 将一个指针变量的值赋给另一个指针变量。
让指针变量指向某一变量,就是将一个变量的地址存入指针变量。但程序员并不知道变量在内存中的地址,为此,C语言提供了一个取地址运算符&。&运算符是一个一元运算符,运算对象是一个变量,运算结果是该变量对应的内存地址。例如,定义
int *p,x;
可以用p=&x将变量x的地址存入指针变量p中。指针变量也可以在定义时赋初值,例如
int x,*p=&x;
定义了整型变量x和指向整型的指针p,同时让p指向x。
与普通类型的变量一样,C语言在定义指针变量时只负责分配空间。除非在定义指针变量时为变量赋初值,否则该指针变量的初值是一个随机值。引用该指针指向的空间是没有任何意义的,甚至是很危险的,为了避免这样的误操作,不要引用没有被赋值的指针。如果某个指针暂且不用了的话,可以给它赋一个空指针NULL。NULL是C语言定义的一个符号常量,它的值为0,表示不指向任何地址。NULL可以赋给任何类型的指针变量。在引用指针指向的内容时,先检查指针的值是否为NULL是很有必要的,这样可以确保指针指向的空间是有效的。
在对指针进行赋值的时候必须注意,赋值号两边的指针类型必须相同。否则应使用强制类型转换。
间接访问指针变量指向的变量
定义指针变量的目的并不是要知道某一变量的地址,而是希望通过指针间接地访问某一变量的值。
因此,C语言定义了一个访问指针指向的地址中的内容的运算符*。*运算符是一元运算符,它的运算对象是一个指针。*运算符根据指针的类型,返回其指向的指针变量。例如,有定义:
int x,y;
int *intp;
x=3;
y=4;
intp=&x; //也即:*intp=&x;
*intp=y+5;
printf("%d %d",*intp,x);
//结果:9 9
指针变量的其它操作
指针变量保存的是一个内存地址。内存地址本质上是一个整数。因此对指针变量可以执行算术运算、关系运算、逻辑运算和输出操作。尽管对整数可以执行任何算术运算,但对指针执行乘除运算是没有意义的。
C语言规定对指针只能执行加减运算,指针的加减是考虑了指针的基类型。
统配指针类型void
在标准C语言中,只有相同类型的指针之间能互相赋值,但如何类型的指针都能与void类型的指针互相赋值,因此void类型的指针被称为统配指针类型。
统配指针类型的定义方式如下:
void *指针变量名;
指针与数组
指向数组元素的指针
int a[10],*p;
p=&a[3];
//连续地址
for(int i=0;i<10;i++){
p=&a[i];
printf("%d ",p);
}
指针运算与数组访问
指针的算术运算主要用于数组访问,数组在内存中占有一块连续的空间。
#include<stdio.h>
int main() {
int a[10]= {1,2,3,4,5,6,7,8,9,10},*p;
for(p=a; p<a+10; ++p) {
printf("%d ",*p);
}
}
//控制台输出为:1 2 3 4 5 6 7 8 9 10
指针与函数
指针作为参数
这里先来个经典的案例:交换a,b的值。
错误写法:
#include<stdio.h>
void swap(int a,int b){
int temp;
temp=a;
a=b;
b=temp;
}
int main() {
int a,b;
a=2;
b=3;
swap(a,b);
printf("a=%d;b=%d",a,b);
}
//控制台输出为:a=2;b=3
正确写法:
#include<stdio.h>
void *swap(int *a,int *b){
int temp;
temp=*a;
*a=*b;
*b=temp;
}
int main() {
int a,b,*t;
a=2;
b=3;
t=swap(&a,&b);
printf("a=%d;b=%d",a,b);
}
//控制台输出为:a=3;b=2
这是为什么呢?
因为C语言的参数传递方式是值传递。所谓的值传递是指形式参数有自己的空间,在执行函数调用时,用实际参数值初始化形式参数,以后实际参数和形式参数再无任何关系。不管形式参数如何变化都不会影响实际参数。
为了能使形式参数的变化影响到实际参数,可以将形式参数定义成指针类型。由于形式参数是指针,实际参数必须是一个地址。在函数调用时,将需要交换的变量地址传过去,在函数中用间接访问交换两个形式参数指向的空间中的内容。
用指针作为参数可以在函数中修改调用该函数的函数中的变量值,必须小心使用!
指针传递可以在函数中修改实际参数的值。这一特性使得函数可以有多个返回值。
案例1:设计一个解一元二次方程的函数。
#include<stdio.h>
#include<math.h>
int solve(double a,double b,double c,double *x1,double *x2)
{
double disc,sqrtDisc;
if(a==0)
return 3;
disc=pow(b,2)-4*a*c;
if(disc<0)
return 2;
if(disc==0){
*x1=-b/(2*a);
return 1;
}
sqrtDisc=sqrt(disc);
*x1=(-b+sqrtDisc)/(2*a);
*x2=(-b-sqrtDisc)/(2*a);
return 0;
}
int main() {
double a,b,c,x1,x2;
int result;
printf("请分别输入a,b,c:");
scanf("%lf %lf %lf",&a,&b,&c);
result=solve(a,b,c,&x1,&x2);
switch(result){
case 0:
printf("方程有两个不同的根:x1=%.2f,x2=%.2f",x1,x2);
break;
case 1:
printf("方程有两个等根:x1=%.2f,x2=%.2f",x1,x2);
break;
case 2:
printf("方程无根");
break;
case 3:
printf("该方程不是二元一次方程。");
break;
defalt:break;
}
return 0;
}
案例2:歌德巴赫猜想
#include<stdio.h>
#include<math.h>
void Gede(int n,int *p1,int *p2)
{
int i;
if(n==4){
*p1=2;
*p2=2;
return;
}
for(i=3;i<n;i+=2){
if(isPrime(i)&&isPrime(n-i)){
*p1=i;
*p2=n-i;
break;
}
}
}
int isPrime(int n)
{
int i,max=sqrt(n)+1;
for(i=3;i<max;i+=2)
if(n%i==0)
return 0;
return 1;
}
int main() {
int i,p1,p2;
for(i=10;i<=20;i+=2){
Gede(i,&p1,&p2);
printf("%d=%d+%d\n",i,p1,p2);
}
return 0;
}
返回指针的函数
函数的返回值可以是一个指针。表示函数的返回值是一个指针只需在函数名前加一个*号。返回指针的函数的原型为:
类型名 *函数名(形式参数表); //参考案例请看上面的“swap函数之a,b数值交换”。
数组作为函数参数的进一步讨论
数组名可以作为函数的形式参数和实际参数。
一些定论:1.数组传递即是指针/地址传递;2.数组传递的本质是地址传递;3.C语言是将形式参数的数组作为指针来处理的。
尽管传递数组时,形式参数既可以写成数组,也可以写成指针,但作为一般规则,声明参数必须能体现出各个参数的用途。如果需要将一个形式参数作为数组使用,并以下标变量的形式访问,那么应该将该参数声明为数组。如果需要将一个形式参数作为指针使用,并且引用其指向的内容,那么应该将该参数声明为指针。当传递的是一个数组时,必须用另一个参数指出数组中的元素个数。
案例:设计一个递归函数,在一个整型数组中找出最大值和最小值。
方法1:
#include <stdio.h>
int findMax(int arr[], int start, int end) {
if (start == end) {
return arr[start];
} else {
int mid = (start + end) / 2;
int leftMax = findMax(arr, start, mid);
int rightMax = findMax(arr, mid + 1, end);
return (leftMax > rightMax) ? leftMax : rightMax;
}
}
int findMin(int arr[], int start, int end) {
if (start == end) {
return arr[start];
} else {
int mid = (start + end) / 2;
int leftMin = findMin(arr, start, mid);
int rightMin = findMin(arr, mid + 1, end);
return (leftMin < rightMin) ? leftMin : rightMin;
}
}
int main() {
int arr[10] = {5, 9, 12, 8, 4, 1, 7, 6, 3, 10};
int max = findMax(arr, 0, 9);
int min = findMin(arr, 0, 9);
printf("最大值为:%d\n", max);
printf("最小值为:%d\n", min);
return 0;
}
方法2:
#include <stdio.h>
void findMinMax(int *arr, int size, int *min, int *max) {
*min = *max = arr[0]; // 将数组的第一个元素同时赋值为最小值和最大值
for(int i = 1; i < size; i++) {
if (arr[i] < *min) {
*min = arr[i]; // 将当前元素赋值为最小值
}
if (arr[i] > *max) {
*max = arr[i]; // 将当前元素赋值为最大值
}
}
}
int main() {
int arr[] = {5, 9, 2, 7, 66, 8, 6, 4, 3, 12};
int size = sizeof(arr) / sizeof(arr[0]);
int min, max;
findMinMax(arr, size, &min, &max);
printf("Minimum value: %d\n", min);
printf("Maximum value: %d\n", max);
return 0;
}
指针这一模块我分了三个章节来梳理,感兴趣可以继续查看下一篇文章。