指针

以下讲解均来自翁惠玉老师出版的书:《第一行代码·C语言》。大家感兴趣的可以去搜索看一下哈!

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;
}

C语言之指针(上)_赋值_02

案例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;
}

C语言之指针(上)_赋值_03

返回指针的函数

函数的返回值可以是一个指针。表示函数的返回值是一个指针只需在函数名前加一个*号。返回指针的函数的原型为:

类型名 *函数名(形式参数表);  //参考案例请看上面的“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;
}

C语言之指针(上)_数组_04

指针这一模块我分了三个章节来梳理,感兴趣可以继续查看下一篇文章。