C语言-基础入门-学习笔记(10):指针精讲(最详尽的使用指南)

C语言-基础入门-学习笔记(10):指针精讲(最详尽的使用指南)

指针一直是我最难懂的地方,我相信也是最令大家头疼的地方,所以在这里我会带着大家详细地学习一下指针,希望大家能够通过这篇文章完全地搞懂指针是什么,不在受它的折磨,我也会将我看到的一些好的资料放出来供大家参考!!

一、指针概述

C语言具有获取变量地址和操纵地址的能力,而用来操作地址的这种特殊数据类型就是指针。
简单来说,指针就是一种数据类型,用来表示内存地址,因此使用指针变量就可以对内存进行灵活的操作。

1.访问内存的两种方式

  1. 直接访问:每一个被定义的变量都有两个属性:变量值和变量地址。变量地址表示了该变量在内存中存储的位置,变量值就是该内存中的内容。因此要访问该空间上的内容可以直接使用变量名。
int a = 241;
printf("%d",a);
  1. 间接访问:可以使用地址操作符 & 来获得变量的地址。再使用指针操作符来获得该地址的内容。
int a = 241;
*(&a);

2.指针的概念

表示内存地址的数据类型就是指针类型。所以,地址就是指针,一个变量的地址就是一个指针型常量,用来保存地址的变量就是一个指针型变量。

假设变量名为a,它的内容为241,它存储在了地址为0016的地方。
它的指针名字为b,由于地址就是指针,那么该指针的值为a的地址,即0016,指针的地址为1010。
所以这里b叫作指针变量。
指针变量的属性:
1、指针变量的地址,即为指针变量分配的内存空间地址。(1010)
2、指针变量的值,指针内存空间上的内容。(0016)
3、以指针变量的值为地址的空间地址,也称指针指向的空间地址。(0016)
4、指针指向的内存空间的内容。(241)

虽然指针变量的值和其指向空间地址的值相同,但指针变量的值为一个内存空间的内容,是可以改变的。而指针变量指向的空间的地址是一个内存空间的地址,是一个常量,不可以被改变。

3.指针变量的定义

指针类型由两部分组成:数据类型名和指针操作符。 其中数据类型名声明了指针变量指向的内存空间存储的数据类型。
数据类型名 * 指针变量名

char * cp;//指针变量cp指向一个char型内存空间
int * ip;//指针变量ip指向一个int型内存空间
float * fp;//指针变量fp指向一个Int型内存空间

范例1

#include <stdio.h>

int main(void){
	printf("sizeof(int *) = %d\n",sizeof(int *));
	printf("sizeof(char *) = %d\n",sizeof(char *));
	printf("sizeof(float *) = %d\n",sizeof(float *));
	return 0;
}

在这里插入图片描述
指针类型所占空间与其指向的内存存储的数据类型无关。

二、指针的使用

1.指针变量的赋值

为指针变量赋值就是为指针设定它指向的内存空间的过程。给指针变量赋值必须赋给它一个地址。

char c = 'a';
char * cp;
cp = &c;//指针指向c的地址,用&取c的地址

printf("%c",c);
printf("%c",*cp);//这句打印与上面一句是等价的,都是将c打印出来

范例2
指针可以访问和修改变量

#include <stdio.h>

int main(void){
	int a = 2;
	int *b;
	b = &a;

	printf("\ta = %d\n",a);//直接访问
	printf("\t*b = %d\n",*b);//间接访问

	*b = 3;//改变指向空间的内容

	printf("After \"*b = 3\":\n");
	printf("\ta = %d\n",a);
	printf("\t*b = %d\n",*b);

	return 0;
}

在这里插入图片描述
范例3
指针变量的值在一次赋值后,可以再次被赋值为其他地址。
在输出符时,对于指针的值、地址符的输出用%p

#include <stdio.h>

int main(void){
	int a = 2;
	int b = 3;
	int *p;

	printf("&a = %p\n",&a);
	printf("&b = %p\n",&b);

	printf("\nAfter definition:\n");
	printf("\t&p = %p\n",&p);
	printf("\tp = %p\n",p);
	//printf("\t*p = %d\n",*p);

	p = &a;//将p赋值为a的地址
	printf("\nAfter \"p = &a\":\n");
	printf("\t&p = %p\n",&p);
	printf("\tp = %p\n",p);
	printf("\t*p = %d\n",*p);

	p = &b;//改变p的指向
	printf("\nAfter \"p = &b\":\n");
	printf("\t&p = %p\n",&p);
	printf("\tp = %p\n",p);
	printf("\t*p = %d\n",*p);

	return 0;
}

在这里插入图片描述
对指针变量赋值的过程,就是使指针变量指向一个新的内存空间的过程。

2.将指针变量赋值为整数

原则上不能将整数数值赋值给指针变量,否则指针变量会指向以该整数数值为地址值的内存空间。
但是如果该整数值空间是有效的,那么可以使用。
范例4
使用scanf函数将整数赋值给指针变量

#include <stdio.h>

int main(void){
	int a = 1;
	int *p;

	printf("a = %d\n",a);
	printf("&a = %p\n",&a);//输出地址时用%p

	printf("Input the address of a: ");
	scanf("%p",&p);

	printf("*p = %d\n",*p);
	printf("p = %p\n",p);

	return 0;
}

在这里插入图片描述

3.初始化指针变量

使用指针时最好为其进行初始化,否则指针将指向一个不可知的空间。

int a = 0;
int *p1 = &a;

int *p2 = NULL;

NULL的值为0,它是指针型数值中约定的0值的表示形式。

4.const指针

  1. 指向const的指针变量
int const *p;
const int *p;

将变量p声明为指向存储const int型数据的内存空间的指针变量,该类指针指向的内存空间的内容是不可变的。

const int a = 1;
const int *p1 = &a;
*p1 = 2;  //*p1已经为1,不能为2
  1. const型指针变量
int *const p;

const型指针变量指向的内存空间是固定的,初始化后不能将其指向其他空间。

int a = 1;
int b = 2;
int *const p = &a;
*p = 12;//正确
p = &b;//指针变量的值不可改变,即不能改变指向的内存空间地址
  1. 指向const的const指针变量
const int *const p;

该指针变量的值和该指针指向的空间的值都是不可改变的。

int a = 1;
int b = 2;
int const *const p = &a;
*p = 12;//错误
p = &b;//错误

三、指针与函数

指针变量也可以作为函数参数使用。

1.指针形参

函数调用时,将实参的值赋给形参。使用指针变量作为函数参数,可以将一个内存空间的地址传递到函数中,可以通过该地址来操作该地址上的内存空间。

void func(int *pt,int a);

int *p;
int x,y;
x = 5;
y = 20;
p = &x;
func(p,y);

这里实参y传给形参a,则将a赋值为20;而p为x的地址,将p赋给指针*pt,则pt所指向的内容为5。

指针可以做到在函数内改变函数外的值,通过地址来传递值,这种方式称为地址传递。

范例5
使用指针作为形参的函数实现两个数的交换

#include <stdio.h>

void swap(int *pt1,int *pt2){
	//输入一个中间变脸
	int tmp;

	printf("pt1 = %p\n",pt1);
	printf("pt2 = %p\n",pt2);
	printf("*pt1 = %d\n",*pt1);
	printf("*pt2 = %d\n",*pt2);
	//用指针进行交换
	tmp = *pt1;
	*pt1 = *pt2;
	*pt2 = tmp;

	printf("*pt1 = %d\n",*pt1);
	printf("*pt2 = %d\n",*pt2);
}

int main(void){
	int a,b;
	int *p1 = NULL;
	int *p2 = NULL;

	printf("Please input two numbers: ");
	scanf("%d",&a);
	scanf("%d",&b);

	p1 = &a;
	p2 = &b;
	//交换前的信息
	printf("p1 = %p\n",p1);
	printf("p2 = %p\n",p2);
	printf("*p1 = %d\n",*p1);
	printf("*p2 = %d\n",*p2);

	swap(p1,p2);
	//交换后的信息
	printf("*p1 = %d\n",*p1);
	printf("*p2 = %d\n",*p2);

	return 0;
}

在这里插入图片描述

通过上面的指针使用,我大致总结出了如下规律:
定义指针时一般为ptr;在使用时将ptr=&变量,相当于将变量的地址赋给指针变量ptr;而想要取地址的内容的时候使用ptr即可将该变量的值取出来。

指针形参的使用方法:

  1. 定义一个含有指针变量形参的函数;
  2. 在主调函数中为该变量分配空间,并将一个指针变量指向该空间;
  3. 以这个指针变量为实参调用定义好的函数;
  4. 在函数内改变该指针指向的值;
  5. 函数返回后,主调函数中的变量已经被改变。

2.指针型函数

函数的函数返回值也可以是指针型的数据,即地址。返回该类型值时,执行机制与返回其他类型完全相同。声明方式一般为:
数据类型 * 函数名(形参列表)

int * max(int a,int b,int c);
//此max函数中的return语句必须返回一个变量的地址或一个指针变量的值。

范例6
使用指针变量作为函数返回值

#include <stdio.h>

int * max(int *a,int *b,int *c){
	int *p = NULL;

	if(*a > *b)//如果a指向的值较大,将p设为a,否侧设为b
		p = a;
	else
		p = b;
	if(*p < *c)//如果c指向的值较大,将p设为c
		p = c;

	return p;
}

int main(void){
	int a = 0;
	int b = 0;
	int c = 0;
	int *p = NULL;

	printf("Please input three integers: ");
	scanf("%d",&a);
	scanf("%d",&b);
	scanf("%d",&c);
	//由于函数的形参是指针型,所有传入变量的地址
	p = max(&a,&b,&c);
	printf("The max is %d.\n",*p);

	return 0;
}

在这里插入图片描述
此段程序通过将a,b,c三个数的地址传入到max函数中,通过指针的方式找到他们所对应的的内存地址的值,再进行对比。返回时返回的是一个指针变量,p相当于得到的最大值得地址,再通过*p来取地址,得到这个最大值。

3.函数型指针

函数也是存放在代码区内,他们也有地址。如果要将函数变量赋值给其他变量,就需要用到函数型指针函数型指针就是指向函数的指针。

函数变量名的地址为函数的地址,函数的值保存为这个函数的地址。
假设有下面一个函数:

int func(const int a,const int b);

此时函数名为func,则函数的地址为func的地址,函数的值保存在func的这个地址内。

使用该函数类型来定义一个函数型指针,方式如下:

int (* fp)(const int a,const int b);

这里*fp两侧的小括号不可省! 下面为该函数型指针赋值:

fp = func;
(*fp) (5,6);

由于fp被赋值为函数变量func的地址,而func的值又等于其地址,所以*fp可以得到func函数的地址。

函数型指针变量赋值时,左值与右值的类型必须完全一致。

范例7
使用函数型指针来调用函数

#include <stdio.h>

int add(const int a,const int b){
	return a + b;
}

int main(void){
	int (*fp)(const int a,const int b);//定义函数型指针

	fp = add;//将其赋值为add
	printf("3 + 4 = %d\n",fp(3,4));
	printf("3 + 4 = %d\n",(*fp)(3,4));

	printf("%p\n",add);
	printf("%p\n",&add);
	printf("%p\n",fp);
	printf("%p\n",*fp);

	return 0;
}

在这里插入图片描述
可以看出函数的地址即函数的值。

4.void型指针

void型指针就是无类型指针。void型指针可以指向存储任意数据类型端的空间。 定义形式如下:
void * 变量名;

void * p1 = "Goodbye";
int * p2 = p1;  //可以赋值为任意类型的地址值,也可以用来作为赋值表达式的右值。

不能对void型指针做加减运算,因为对指针端的加减是基于指针指向的类型空间的字节长度进行的。void型指针变量的地址空间类型是未知的,因此不能进行加减。

**void型指针通常用作函数的形参。**声明如下:
void * 函数名(形参类型);

void *func(int a);

int *p1 = NULL;
int *p2 = NULL;
····
p1 = (int *)func(sizeof(int));
p2 = (char *)func(sizeof(int));

练习1

//使用指针从标准输入获取三个整数,并求其中最大值
#include <stdio.h>

int main(void){
	int a,b,c;

	int *p1 = &a;
	int *p2 = &b;
	int *p3 = &c;

	scanf("%d %d %d",p1,p2,p3);

	if(*p1 < *p2){   //比较地址中的内容
		p1 = p2;	//将p2地址赋给p1
	}
	if(*p1 < *p3){
		p1 = p3;
	}
	printf("Max is %d\n",*p1);

	return 0;
}

在这里插入图片描述
练习2

//实现一个可以初始化各种类型数据的函数
#include <stdio.h>

void initial(void *p,char type){
	switch(type){
	case 'i':				//int型
		*((int *)p) = 0;	//强制类型转换为int型指针,并取内容
		break;
	case 'c':				//char型
		*((char *)p) = '\0';//强制类型转换为char型指针,并取内容
		break;
	case 'l':				//long long型
		*((long long *)p) /= 2;//强制类型转换为long long型指针,并取内容
		break;
	case 'f':				//double型
		*((double *)p) = 0.0;//强制类型转换为double型指针,并取内容
		break;
	case 'p':				//指针类型
		*((char **)p) = NULL;//强制类型转换为任意型指针,并取内容
		break;
	default:				//错误类型
		printf("Error type!\n");
		break;
	}
}

四、指针与数组

指针也可以用来指向数组或指向数组中的一个元素。当指针指向数组时,可以通过指针来访问数组中的元素。

1.指向数组元素的指针

指向数组元素的指针是指一个指针变量,其指向的空间属于一个数组中的某一个元素。例如:

int array[10] = {0};

int *p0 = &array[0];
/*等效于*/
int *p0 = array; 

这是因为数组的第0个元素的地址就是数组的地址。

2.指针访问数组

在数组中,相邻元素的地址只差一个元素的字长。因此,可以用一个数组元素型指针加上要访问的数组元素的地址偏移量即可获得该元素的地址。

范例8

#include <stdio.h>
#define SIZE 10

int main(void){
	char a_ch[SIZE] = "Student";
	int a_int[SIZE] = {1,2,3,4,5,6,7,8,9,10};

	char *p_ch = a_ch;
	int *p_int = a_int;
	//使用索引法输出两个元素的地址
	printf("&a_ch[4] = %p, &a_ch[3] = %p\n",&a_ch[4],&a_ch[3]);
	//使用索引法输出两个元素之间的地址偏移量
	printf("&a_ch[4] - &a_ch[3] = %d\n\n",&a_ch[4]-&a_ch[3]);
	//使用索引法输出两个元素的地址
	printf("&a_int[4] = %p, &a_int[3] = %p\n",&a_int[4],&a_int[3]);
	//使用索引法输出两个元素之间的地址偏移量
	printf("&a_int[4] - &a_int[3] = %d\n\n",&a_int[4]-&a_int[3]);
	//使用指针法输出两个元素的地址
	printf("p_ch + 4 = %p,p_ch + 3 = %p\n",p_ch+4,p_ch+3);
	//使用指针法输出两个元素之间的地址偏移量
	printf("p_int + 4 = %p,p_int + 3 = %p\n",p_int+4,p_int+3);

	return 0;
}

在这里插入图片描述
在计算两个地址的差的时候,为实际差/存储类型占字节大小

3.数组指针和数组变量

可以通过数组名和地址偏移量的组合来访问数组元素。

int array[5] = {1,2,3,4,5};
printf("%d\n",*(array+2));

数组元素型指针与数组变量在很多方面是不同的:

  1. 值的可改变性不同
    数组元素型指针的值是可以改变的,而数组变量的值是不能改变的。
//可行
int array_1[20] = {0};
int array_2[20] = {0};
int *p = array_1; 
p = array_2;	//使指针指向另一个数组
p++;	//使指针指向数组array_2[1]

//不可行
array_1++;	//试图使array_1的内容为array1[1],错误
array_1 = array_2;	//错误
  1. 占用空间不同
    数组元素型指针变量占用的空间是固定的,由系统决定。数组变量占用的空间是由程序决定的。
  2. 访问方式不同
    数组元素型指针访问方式为间接访问,而数组变量为直接访问。

范例9

#include <stdio.h>
#define SIZE 5

int main(void){
	int a_int[SIZE] = {1,2};
	int *p_int = a_int;

	//输出数组元素型指针和数组变量的占用空间
	printf("sizeof(a_int) = %d\n",sizeof(a_int));//数组变量
	printf("sizeof(p_int) = %d\n\n",sizeof(p_int));//指针变量

	//输出数组第0个元素的值及其地址
	printf("a_int[0] = [%d]\n",a_int[0]);//第0个元素
	printf("&a_int[0] = [%p]\n\n",&a_int[0]);//第0个元素的地址

	//输出数组变量的三个属性
	printf("*a_int = [%d]\n",*a_int);//a_int指向的内容
	printf("a_int = [%p]\n",a_int);//a_int的值
	printf("&a_int = [%p]\n\n",&a_int);//a_int的地址

	//输出数组元素型指针的三个属性
	printf("*p_int = [%d]\n",*p_int);//p_int指向的内容
	printf("p_int = [%p]\n",p_int);//p_int的值
	printf("&p_int = [%p]\n",&p_int);//p_int的地址

	return 0;
}

在这里插入图片描述
从上面例子可以得到:数组变量的值=第2个元素的地址=数组变量的地址;而数组元素型指针的值=第1个元素的地址,但它的地址是另外一个地址。

4.数组指针作为函数形参

当数组作为函数形参时,数组的地址可以用作函数调用的实参。声明形式有以下几种:

//形式1
void print_array(int array[SIZE]);
//形式2
void print_array(int array[]);
//形式3
void print_array(int * array);

范例10

//验证上述3种方法都是指针变量
#include <stdio.h>
#define SIZE 6

void func_1(const char a[SIZE]){
	printf("char a[SIZE]: %d\n",sizeof(a));
}

void func_2(const char a[10000]){
	printf("char a[10000]: %d\n",sizeof(a));
}

void func_3(const char *a){
	printf("char *a: %d\n",sizeof(a));
}

void func_4(const char a[]){
	printf("char a[]: %d\n",sizeof(a));
}

int main(void){
	char a_char[SIZE] = {'a','b','c','d','e','f'};
	char * p_char =a_char;  //定义字符指针

	printf("sizeof(a):\n");
	func_1(a_char);
	func_2(p_char);
	func_3(a_char);
	func_4(p_char);

	return 0;
}

在这里插入图片描述
一般情况下,为了获得数组容量,通常将形式2和形式3改写为如下:

//形式2
void print_array(int array[],int size);
//形式3
void print_array(int * array,int size);

这种方式还可以在函数开始时对数组容量进行判 断,提高程序安全。

5.调用含数组形参的函数

调用含数组形参的函数时,数组变量和数组元素型指针变量都可以作为实参。但不论是数组变量还是数组元素型指针变量,他们的值都是数组的首地址。

范例11
使用数组作为形参,实现了可输出升序数组中大于或等于0的数组元素的函数

#include <stdio.h>
#define SIZE 6

void print_array(const int a[],const int n){
	//n为要输出数组元素的个数
	int i = 0;
	for(i=0;i<n;i++){    //从a开始的n个元素
		printf("%4d",a[i]);
	}
	printf("\n");
}

int main(void){
	int a_int[SIZE] = {-55,-4,2,7,18,99};	//初始化一个升序序列

	int *p = a_int;

	while(*p < 0)
		++p;		//将p后移
	print_array(p,SIZE - (p-a_int));//输出p开始的剩余数组元素

	return 0;
}

在这里插入图片描述
调用函数时,指针变量型数组的形参会被赋值为数组的首地址(数组元素型指针变量)。

五、指针与二维数组

1.二维数组的地址

对于2维数组,其在内存中是转换为1维数组的形式来存储的。首地址表示形式:

  • 将二维数组看成一个整体,其地址为a,这也是二维数组的地址;
  • 二维数组变量的值就是该二维数组的首地址;
  • 首地址等于数组中第0个元素的地址

在使用过程中,可以将一些含糊不清的变量或数值赋给肯定不匹配的变量,来诱发编译器产生对数据类型不匹配的警告信息。从而得知某变量的数据类型。

int b = &a;

一般情况下,第m行第n列元素的地址有以下表示方法

  • 将该元素视为第m行第n列元素的地址,为 &a[m][n]
  • 若使用指针法表示,为 *(a+m)+n
  • 将3x2的int型二维数组视为可存放6个int型数据的一维数组,那么第m行第n列元素为第(2xm+n)个元素,因此可以表示为&a[0][0]+2*m+n,即 * a+2 *m+n

范例11

#include <stdio.h>

#define ROW_SIZE 3
#define COL_SIZE 4

int main(void){
	//初始化一个二维数组
	int array[ROW_SIZE][COL_SIZE] = {
		{1,2,3,4},
		{11,12,13,14},
		{21,22,23,24}
	};

	//输出各种变量的字节长度
	printf("sizeof(array) = %d\n",sizeof(array));//12 x 4 = 48
	printf("sizeof(array[1]) = %d\n",sizeof(array[1]));//4 x 4 = 16
	printf("sizeof(array[1][0]) = %d\n",sizeof(array[1][0]));//1 x 4 = 4
	//输出数组首地址的各种形式的值
	printf("&array\t\t = %p\n",&array);
	printf("array\t\t = %p\n",array);
	printf("&array[0]\t = %p\n",&array[0]);
	printf("&array[0][0]\t = %p\n",&array[0][0]);

	printf("*array\t\t = %p\n",*array);
	printf("array[0]\t = %p\n",array[0]);

	//输出数组中第1个一维数组首地址的各种表达形式的值
	printf("*(array+1)\t = %p\n",*(array+1));
	printf("&array[1]\t = %p\n",&array[1]);
	printf("&array[1][0]\t = %p\n",&array[1][0]);

	printf("*array+4\t = %p\n",*array+4);

	printf("array[1]\t = %p\n",array[1]);

	//比较*(array+1)和(*array+1)的不同
	printf("*(array+1) - &array[0][0] = %d\n",*(array+1) - &array[0][0]);
	printf("(*array+1) - &array[0][0] = %d\n",(*array+1) - &array[0][0]);

	return 0;
}

在这里插入图片描述

2.指针法访问二维数组

使用指针法访问二维数组有三种方式:使用指向数组元素的指针、使用一维数组型指针和使用二维数组型指针
1. 使用指向数组元素的指针

int a[3][2] = {{1,2},{11,12},{21,22}};
int *p = *a;	//即&a[0][0]
这里是将数组第0个元素的地址赋值给指针变量,而不是将数组首地址赋值给指针变量。

由于3X2二维数组中第i行第j列元素可以表示为如下形式。
*(*a + i * 2 +j)
使用p代替*a,可得表达式如下:
*(p + i * 2 + j)

2. 使用一维数组型指针
对于一维数组型指针,应该将其赋值为数组第0个一维数组的地址。

int a[3][2] = {{1,2},{11,12},{21,22}};
int (*p)[2] = a;	//即&a[0]

由于a中第m行第n列元素的地址可以表示为如下形式:
*(*(a+m)+n)
使用p来代替a,可得如下表达式:
*(*(p+m)+n)

3. 使用二维数组型指针

int a[3][2] = {{1,2},{11,12},{21,22}};
int (*p)[3][2] = &a;

那么数组a中第m行第n列元素可以表示为:
*(*(*p+m)+n)	//表示第m个1维数组地址下的第n个偏量*(**p+2*m+n)	//第m行n列元素相对第一个元素的偏移量为(2 * m + n)

3.二维数组形参

二维数组的地址可以用做函数的形参。在传递二维数组地址时,可以使用三种不同的指针作为形参来获得二维数组。

1. 数组元素型指针
将数组第一个元素的地址作为实参赋值给形参,在函数中使用该首地址和元素偏移量的组合来获得数组元素的地址,通过地址访问元素。

范例11

#include <stdio.h>

#define ROW_SIZE 3
#define COL_SIZE 3

void print_array(int *p,const int row,const int col){
	int i,j;
	for(i=0;i<row;i++){
		for(j=0;j<col;j++)
			printf("%4d",*p++);
		printf("\n");
	}
}


void reverse(int *p,const int row,const int col){
	int i,j;
	for(j=0;j<col;j++){
		for(i=0;i<row;i++)
			printf("%4d",*(p+i*col+j));//首地址和元素偏移量组合
		printf("\n");
	}
}

int main(void){
	int array[ROW_SIZE][COL_SIZE] = {
		{1,2,3},
		{11,12,13},
		{21,22,23}
	};

	printf("Output the matrix:\n");
	print_array(*array,ROW_SIZE,COL_SIZE);//以*array为第一个实参调用函数

	printf("Reverse the matrix:\n");
	reverse(&array[0][0],ROW_SIZE,COL_SIZE);//以&array[0][0]为第一个实参调用函数

	return 0;
}

在这里插入图片描述

2. 使用一维数组型指针
可以使用一维数组型指针作为形参来传递二维数组。

范例12

#include <stdio.h>

#define ROW_SIZE 3
#define COL_SIZE 3

void print_array(int (*p)[COL_SIZE],const int row){
	/*数组型指针作为变量*/
	int i,j;
	for(i=0;i<row;i++){
		for(j=0;j<COL_SIZE;j++)
			printf("%4d",*(*p+j));//使用数组型指针访问数组元素
		printf("\n");
		++p;
	}
}


void reverse(int (*p)[COL_SIZE],const int row){
	int i,j;
	for(j=0;j<COL_SIZE;j++){
		for(i=0;i<row;i++)
			printf("%4d",*(*(p+i)+j));
		printf("\n");
	}
}

int main(void){
	int array[ROW_SIZE][COL_SIZE] = {
		{1,2,3},
		{11,12,13},
		{21,22,23}
	};

	printf("Output the matrix:\n");
	print_array(array,ROW_SIZE);//以array为实参调用函数

	printf("Reverse the matrix:\n");
	reverse(&array[0],ROW_SIZE);//以&array[0]为实参调用函数

	return 0;
}

在这里插入图片描述
3. 使用二维数组型指针

范例13

#include <stdio.h>

#define ROW_SIZE 3
#define COL_SIZE 3

void print_array(int (*p)[ROW_SIZE][COL_SIZE]){
	int i,j;

	for(i=0;i<ROW_SIZE;i++){
		for(j=0;j<COL_SIZE;j++)
			printf("%4d",*(*(*p)+i*COL_SIZE+j));//方式1
			//printf("%4d",*(*(p+i)+j));//方式2
		printf("\n");
	}
}

int main(void){
	int array[ROW_SIZE][COL_SIZE] = {
		{1,2,3},
		{11,12,13},
		{21,22,23}
	};

	printf("Output the matrix:\n");
	print_array(&array);

	return 0;
}

在这里插入图片描述

六、指针与字符

1.字符指针

字符指针是指向字符型内存空间的指针变量,一般定义语句如下:

char *p = NULL;
  1. 访问字符数组:将字符指针赋值为字符数组的首地址即可实现对字符数组的访问。
char str[] = "Hello,world!";
char *p = str;
printf("%c",*(p+i));	//访问str中的某个元素
printf("%s",p);			//打印整个字符数组

同时也可以使用p改变字符数组中的内容,例如:
for(i=0;i<sizeof(str);i++)
	*(p+i) = '\0';
  1. 访问字符串:将字符串指针赋值为字符串常量的首地址。
char *p = "Hello World!";
printf("%c",*(p+i));	//访问str中的某个元素
printf("%s",p);			//打印整个字符数组

由于字符串常量是不可改变的,因此不能用p来改变字符串的内容

范例14

#include <stdio.h>

int main(void){
	char *p1 = "Hello, world!";
	char *p2 = "Hello, world!";
	char *p3 = "Hello, world";	//定义了一个与上面两个不同的字符串

	printf("%p\n",p1);
	printf("%d\n",sizeof(p1));

	printf("%p\n",p2);
	printf("%d\n",sizeof(p2));

	printf("%p\n","Hello, world!");
	printf("%d\n",sizeof("Hello, world!"));

	printf("%p\n",p3);
	printf("%d\n",sizeof(p3));

	return 0;
}

在这里插入图片描述

2.字符指针使用示例

范例15
使用字符指针实现字符串的比较功能,字符串间的大小比较规则与strncmp函数相同。

#include <stdio.h>
#define SIZE 256

int main(void){
	//初始化2个字符数组
	char str1[SIZE] = "\0";
	char str2[SIZE] = "\0";
	//定义两个字符指针指向这两个数组
	char *p1 = str1;
	char *p2 = str2;

	printf("Please input the first string: ");
	gets(p1);
	printf("Please input the second string: ");
	gets(p2);

	while(*p1 == *p2 && '\0' != *p1){
		++p1;	//将p1后移
		++p2;	//将p2后移
	}

	//根据p1和p2指向的值来判断比较结果
	if(*p1 == *p2)
		printf("They are same.\n");
	else if(*p1 > *p2)
		printf("The first one is larger than the second one.\n");
	else
		printf("The first one is smaller than the second one.\n");
	
	return 0;
}

在这里插入图片描述
范例16
使用字符指针将一个字符串的内容复制到另一个字符串中。

#include <stdio.h>
#define SIZE 256

int main(void){
	char str1[SIZE] = "\0";
	char str2[SIZE] = "\0";
	char *p1 = str1;
	char *p2 = str2;

	printf("Please input the source string:\n");
	gets(p1);
	//由于两个字符数组大小相同,因此不用考虑字符溢出
	while('\0' != *p1)
		*p2++ = *p1++;

	p2 = str2;
	printf("The destination string is:\n");
	puts(p2);

	return 0;
}

在这里插入图片描述
*号和++属于同一优先级,且方向都是从右向左的,*p++和 *(p++)作用相同。
在这里的话*p2++和 *p1++的作用是先p1++进行移位,然后在用 *取其中的内容,实现一位一位地复制。

3.字符指针数组

在使用字符指针数组时,如果不指定字符长度,会使每一个字符串的空间都至少扩大为最长字符串所占空间,这样会对资源产生浪费。因此可以使用字符指针数组,在数组中保存字符指针,每个指针指向特定的字符常量。

范例17
使用啊字符指针数组对字符串进行排序

#include <stdio.h>
#include <string.h>
#define NUM 5

int main(void){
	char * p[NUM] = {NULL};
	int i,j;
	char * tmp = NULL;

	//数组内的有效字符指针个数必须等于NUM
	p[0] = "Nelson Aldrich";
	p[1] = "A. Piatt Andrew";
	p[2] = "Frank Vanderlip";
	p[3] = "Henry P.Davison";
	p[4] = "Charles D.Norton";

	//对数组中的字符串排序
	for(i=0;i<NUM-1;i++){
		for(j=i+1;j<NUM;j++){
			if(0<strcmp(p[i],p[j])){
				//互换两个指针指向的字符串
				tmp = p[i];
				p[i] = p[j];
				p[j] = tmp;
			}
		}
	}

	for(i=0;i<NUM;i++)
		puts(p[i]);

	return 0;
}

在这里插入图片描述
当处理多个长度不一的字符串时,应当使用字符指针数组,而不是字符串数组。

练习1
设计一个函数实现strlen函数的功能,即要求返回传入字符串的字符长度.

#include <stdio.h>
#define SIZE 256

int my_strlen(char *p){
	int count = 0;

	while(*p != '\0'){   //如果*p为字符终止符,则结束循环
		++p;	//后移*p
		++count;	//有效字长加1
	}

	return count;
}

int main(void){
	char str[SIZE] = "\0";

	puts("Please input a string:");
	gets(str);
	//*p = str,是一种指针访问数组的方式
	printf("The length of this string is %d.\n",my_strlen(str));

	return 0;
}

在这里插入图片描述
练习2
设计一个取子字符串的函数,函数声明包括以下信息:
源字符串、子字符串开始位置、子字符串字符个数

#include <stdio.h>
#define SIZE 256

char *my_substr(char *src,			//源字符串
				const int size_src,	//源字符串空间大小
				char *dest,			//目的字符串
				const int size_dest,//目的字符串空间大小
				const int start,	//子字符串开始位置
				const int n)		//子字符串字符个数
{
	char *tmp = dest;
	int count = 0;
	//检测各类异常
	if(NULL == src || NULL == dest || start >= size_src || n > size_dest){
		printf("Error in function my_substr.\n");

		return NULL;	//如果错误,返回
	}

	//实现子字符串的复制
	for(src += start,count = 0;
		count < n && *src != '\0';
		++count)
		*dest++ = *src++;

	return tmp;
}

int main(void){
	char p[SIZE] = "\0";
	char sub_str[SIZE] = "\0";
	int start = 0;
	int n = 0;

	puts("Please input a string:");
	gets(p);
	printf("Please input the start position of sub-string:");
	scanf("%d",&start);
	printf("Please input the length position of sub-string:");
	scanf("%d",&n);
	puts(my_substr(p,SIZE,sub_str,SIZE,start,n));

	return 0;
}
}

在这里插入图片描述

发布了55 篇原创文章 · 获赞 76 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_42826337/article/details/102947042