指针的学习:C语言实现

填坑与复习C语言中指针的各个知识点,

本文包含多个实例,具体请点击目录索引查看

 

一、指针变量的定义与使用

(1)两个指针变量之间仅支持相减运算

(2)指针变量仅支持加减运算。

(3)指针作为函数参数

二、指针操作数组

(1)指针、数组、函数综合练习

(2)用const修饰指针变量

1. const int *p = &a;

2.int * const p = &a;

3.const  int * const p = &a;

三、动态内存

(1)实现数组的动态分配大小

(2)多级指针

(3)跨函数使用动态内存

(4)不使用多级指针的跨函数使用动态内存

(5)指针引用二维数组

 


一、指针变量的定义与使用

定义时:

  • 基类型 * 指针变量名
  • int * x;
  • float * y;

使用时:

  • *x代表的是指针变量x所指向的内存单元里的内容(实际值)      
  • x代表指向该内存单元的地址
  • 指针变量必须要初始化,若不知指向何地址,指定为NULL(前提# include <stdio.h>,包含NULL的宏定义)
  • 假设int *j = NULL,NULL指向的是内存中地址为0的内存空间,而在操作系统中,该内存单元是不可用的
  • 切记:不要往存放NULL地址的指针变量里写入数据

以下为错误程序:

# include <stdio.h>

int main(void)
{
    int i = 5;
    int *j = NULL;

    *j = i;
	printf("hello\n");
	printf("j=%d\n",j);

    return 0;
}

你会发现,编译,链接,都不会报错,就执行时候,根本不会打印hello。(科科)

初始化为NULL,与初始化int 为 0 道理差不多,只要将该指针变量再指向其它有意义的可用地址就可以了。

 

在使用时:

  • x代表指针变量(地址值)
  • * x代表指针变量x里面存放的地址 所指向的存储单元的数据(实际值)

 

(1)两个指针变量之间仅支持相减运算

(在同一块内存空间)两个指针变量相减,是一个常量

	int *a,*b,*c;    // a,b,c皆为指针变量
	int j,k;

	int p=3,q=45,r=6789;

	a = &p;            // a,b,c分别指向p,q,r的内存单元(地址)
	b = &q;
	c = &r;
	j = a-b;
	k = a-c;
	printf("a=%d\n",a);
	printf("b=%d\n",b);
	printf("c=%d\n",c);

	printf("j=%d\n",j);
	printf("k=%d\n",k);

结果为:

j = 1 代表相差 1 个元素。 

为什么?

  • 不同类型在内存中所占字节数是不同的,(如int占4B,char占1B),每个字节都有一个地址,指针变量仅存放其所指变量的首地址。
  • 一个int型的变量占4字节,所以一个int元素占4字节,a,b两个地址之间差4个地址,正好是1个int元素。
  • 该特点,在数组中取地址更为明显

 

(2)指针变量仅支持加减运算。

写个小程序吧:从键盘输入10个数整数,输出最大偶数(使用指针访问数组)

if (flag) 的写法还是比较有意思的,flag改成bool型也可

# include <stdio.h>

int main(void)
{
    int a[10] ={0};
	int *p = a;		// a代表该数组首地址

	int max;
	int i;			// 循环变量
	int flag=1;		// 标志位

	printf("输入十个整数\n");
	for(i=0; i<10; i++)
	{
		scanf("%d", p+i);	// 指针变量p 后移1个元素
	}

	for(; p<a+10; p++)
	{
		if(*p % 2 == 0)		// 找到偶数
		{
			if(flag)		// 第一个偶数赋给max(仅执行最多 1 遍)
			{
				max = *p;
				flag = 0;
			}
			else if(*p > max)
			{
				max = *p;	
			}
		}
	}

	if(!flag)
	{
		printf("最大偶数为:%d\n", max);
	}
	else
	{
		printf("未找到偶数\n");		// flag一直为1,数组里压根没偶数元素
	}


    return 0;
}

既然p可以p++,那么数组a首地址可不可以a++?

No,不可以。p是指针变量,a是数组首地址值,为常量。

只有变量可以++/--,常量不行!

 

 

(3)指针作为函数参数

直接操作内存单元的方式来传参。下面以两数交换作为示例:

#include <stdio.h>
void Swap(int *,int *);		//声明一个交换函数

int main()
{
	int a = 3;
	int b = 5;

	printf("a=%d\n", a);
	printf("b=%d\n", b);
	
	printf("a地址为%d\n", &a);
	printf("b地址为%d\n", &b);
	
	Swap(&a, &b);		//对a,b取地址

	printf("a=%d\n", a);
	printf("b=%d\n", b);
		
	printf("a地址为%d\n", &a);
	printf("b地址为%d\n", &b);

	return 0;
}

void Swap(int *p, int *q)
{
	int buf;

	buf = *p;
	*p = *q;
	*q = buf;

	return;
}

成功地交换了俩数实际值。

 

二、指针操作数组

(1)指针、数组、函数综合练习

写一个函数,实现从标准输入流提取字符串的功能:

  1. 模仿gets()与 fgets()函数源码
  2. 利用指针变量操作字符数组,利用*(p+i) 访问数组的各个元素
  3. 注意添加字符串结束标志
/*
	从标准输入流读取字符串	
*/
#include <stdio.h>
char * MyGets(char *, int);   // 函数声明

int main(void)
{
	char str[10]={0};	//申请10个字符大小的数组(实际仅能放8个字符)

	printf("请输入字符串:");

	MyGets(str,10);

	printf("%s",str);	// 无需"\n"

	return 0;
}
char * MyGets(char *p, int n)        // 形参为数组首地址
{
	int i=0;

	*(p+n-1) = '\0';	// 字符串最后一位空间为标志符'\0'
	
	*(p+n-2) = '\n';	// 换行符放结束标志'\0'之前

	for(i=0; i<n-2; i++)
	{
		*(p+i) = getchar();

		if( *(p+i) == '\n')		// 若读到了换行符,则输入字符数少于n-2
		{
			for(i++; i<n-1; i++)
			{
				*(p+i) = '\0';		// 保存换行符,并将其后所有元素置为'\0'		
			}
		}
	}

	return p;
}

倒数第二位为 '\n'换行符; 最后一位为 '\0'结束符

 

(2)用const修饰指针变量

const修饰的变量为:常变量 --> 有名字的不变量--->只读变量--->易维护--->可取代“宏” 

  • 生命周期:程序运行的整个阶段
  • 存储区域:“只读数据段”(跟常量/只读变量一起)
  • 以下三种效果:

1. const int *p = &a;

  • 此时const 与 int可互换位置。
  • const修饰的是 *p
  • *p不可变,其它的都可变。
  • *p代表的是指针变量p所指向的内存单元里的内容(实际值)      
  • p代表指向该内存单元的地址
  • 那么,p可变,指向可变。但指向谁,谁的内容不可变。
  • 常用于定义函数的形参,将形参定义为只读的,避免数据“暴露”。

 

 

2.int * const p = &a;

  • const修饰的是 p
  • p地址值不可变
  • *p实际值可变
  • 即p的指向不可变,p所指向的内存单元的内容可变。

 

3.const  int * const p = &a;

  •  const修饰的是 *p 和 p
  • p和*p都不可变。

 

三、动态内存

(1)实现数组的动态分配大小

  • 要求:使用指针访问数组元素
  • 使用malloc( ) 函数与 realloc( )  (# include <stdlib.h>)
  • void *malloc (unsigned long size);-----> 分配size大小的内存(堆中)----> 返回void * 型的地址
  • void *realloc (void *p, unsigned long size) ---> 定义新长度
/*
	实现数组的动态分配大小
*/
# include <stdio.h>
# include <stdlib.h>

int main(void)
{
	int cnt1, cnt2;		
	int *p;		//指针指向数组首地址
	int i;
	
	printf("请输入数组存放元素个数:");
	scanf("%d", &cnt1);
	p =(int *)malloc(sizeof(*p) * cnt1);		//初始数组所占空间大小
	
	// 扩展到的目标大小
	printf("请输入新元素个数:");
	scanf("%d", &cnt2);
	realloc(p, sizeof(int) * cnt2);

	// 输入
	printf("请输入数组内容,以空格分隔:\n");
	for(i=0; i<cnt2; i++)
	{
		scanf("%d", p+i);		// p+i 等价于 &p[i]
	}

	// 输出
	printf("输出数组内容:\n");
	for(i=0; i<cnt2; i++)
	{
		printf("%d ", *(p+i));
	}
	printf("\n");
	
	// 别忘了释放指针 p,再指向NULL
	free(p);
	p=NULL;

	return 0;
}
  • 静态变量---> 存储到静态存储区 ---> 静态局部变量---> 函数调用结束后不会被释放(作用域:其它函数无法调用)--> 只定义和初始化 1 次
  •                                                    ---> 静态全局变量---> (作用域:只属于此.c文件,其它文件无法访问)
  • 静态内存---> 系统自动分配和回收 --> 分配到栈中 --> 生命周期:函数调用结束,出栈释放
  • 动态内存---> 程序猿全手动分配和回收 --> 分配到堆中 --> 生命周期:若忘了free() 回收,易导致内存泄漏
  • 栈是一种存储结构,而所谓的“堆” ,是指分配内存的排序方式(即堆排序)

 

 

(2)多级指针

多级指针就是——指针的指针的指针...

int ***r ---> 指针变量r 只能存放int ** 型变量的地址 --->  可写成int **(*r) ---> 基类型变成了int **

# include<stdio.h>
int main(void)
{
    int i = 35;
    int *p = &i;    // 指针变量再取地址(指针变量也有地址)
    int **q = &p;
    int ***r = &q;

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

    return 0;
}

输出结果为 i = 35 

 

(3)跨函数使用动态内存

需要解决一个问题:“如何在主调函数中,使用被调函数中动态分配的内存?”

# include <stdio.h>
# include <stdlib.h>

void DynamicArray(int **);	//声明

int main(void)
{
	int *p = NULL;
	DynamicArray(&p);
	printf("**p=%d\n", *p);

	return 0;
}

void DynamicArray(int **q)
{
	*q = (int *)malloc(sizeof(*q));
	**q = 5;
	
	return;
}

 输出结果:*p = 5

  • 指针变量p指向的内存单元里没有内容;
  • p的初始化是在什么时候?DynamicArray函数调用时,构建动态内存空间,p指向这一空间;
  • p是指针变量,指针变量也有自己的地址(系统自动分配的),而p初始化是程序猿的事情;

 

以下是简单变化:给p指向的内存空间添内容

# include <stdio.h>
# include <stdlib.h>

void DynamicArray(int **);	//声明

int main(void)
{
//	int *p = NULL;
	int i = 99;
	int *p = &i;
	printf("*****初始状态*****\n");
	printf("*p=%d\n", *p);
	printf("p的地址值%d\n\n",p);
	
	printf("*****形参分配内存*****\n");
	DynamicArray(&p);		//传参要传int **型的

	printf("*****终态*****\n");
	printf("*p=%d\n", *p);
	printf("p的地址值%d\n",p);
	return 0;
}

void DynamicArray(int **q)		//指针变量q,基类型int *
{
	*q = (int *)malloc(sizeof(*q));
	**q = 5;						// 给*q指向的内存单元添实际值5
	printf("q的地址值:%d\n",q);
	printf("*q的地址值:%d\n",*q);
	printf("该内存单元实际值为:%d\n\n",**q);


	return;
}

 

指针变量p与二级指针*q指向的是同一内存单元

 

稍作改动:

把实参改成p试试

# include <stdio.h>
# include <stdlib.h>

void DynamicArray2(int *);	//声明

int main(void)
{
	int i = 58;
	int *p = &i;
	printf("*****初始状态*****\n");
	printf("*p=%d\n", *p);
	printf("p的地址值%d\n\n",p);
	
	DynamicArray2(p);
	printf("*****后状态*****\n");
	printf("*p=%d\n", *p);
	printf("p的地址值%d\n\n",p);


	return 0;
}

void DynamicArray2(int *q)
{
	q = (int *)malloc(sizeof(*q));
	*q = 566666;
	printf("q的地址值:%d\n",q);
	printf("*q实际值:%d\n\n",*q);
	return;
}

  • 发现并没什么卵用,指针变量q未操作p
  • 指针变量p指向 i ,q也指向 i ,即q存放了 i 的地址
  • 随后调用函数,给q重新分配了空间,q不再指向 i 。Game Over

 

如何理解?因为指针变量 p 为 int * 型,所以 &p 为 int ** 型,所以形参必须为 int ** 型才可传递

 

(4)不使用多级指针的跨函数使用动态内存

动态分配的地址,作为函数返回值才有意义

永远不要返回局部变量的地址

# include <stdio.h>
# include <stdlib.h>
int * DynamicArray3(void);

int main(void)
{
	int *p = DynamicArray3();
	printf("p=%d\n", p);
	printf("*p=%d\n", *p);

	return 0;
}

int * DynamicArray3(void)
{
	int *q = (int *)malloc(sizeof(*q));

	*q = 99;

	return q;
}

结果为 *p = 99

 

(5)指针引用二维数组

假设定义数组int a[3][4];

数组名a,代表的并不是a[0][0]的地址,而是a[0]的地址

有以下推导:

  • a == &a[0]
  • a[0] == &a[0][0]
  • a == &(&a[0][0])

 

a数组包括3行,a[0]、a[1] 和 a[2] 每个行元素都可看做含有4个元素的一维数组。

  • a[0]、a[1] 和 a[2] 既然是一维数组名,那么,该数组名就表示第一个元素的地址。
  • a[0] == &a[0][0] 

有以下推导:

  • a[i] == &a[i][0]
  • a[i] +j == &a[i][j]
  • *(a+i) == &a[i]
  • *(a+i) +j == &a[i][j]

以上记住红色字体即可。

 

方案一:使用指针变量p,指向二维数组a (贼别扭的定义方式,不推荐)

  • 对于a[M][N]而言,二维数组名a是地址的地址
  • 数组名 a 的类型为 int(*)[N]
  • 所以,先把指针变量 p 定义为 int(*)[N]型才能赋值
  • int (*p) [N] = a;                              // 不能直接将a赋值给p

有以下推导:

  • p == a                            // 指针变量 p 指向首地址a[0]
  • a+i == &a[i]
  • p+i ==  &a[i]
  • *(p+i) == a[i]
  • *(p+i) + j == a[i] + j
  • *(p+i) + j == &a[i][j]             // 每个元素的地址
  • *(*(p+i) + j) == a[i][j]           // 取到实际值

 

方案二:稍作变化,将 &a[0][0] 赋值给指针变量 p

  • int *p = &a[0][0];
  • p == &a[0][0]
  • p + i*N + j == &a[i][j]
  • *(p + i*N + j) == a[i][j]              // 取到实际值
  • 其中N代表列数,对于二维数组a[M][N]

 


# include <stdio.h>
int main()
{
	int a[3][4] = {1,2,3,4,10,20,30,40,22,44,66,88};
	int i, j;
	int *p = &a[0][0];

	for( i=0; i<3; i++)
	{
		for( j=0; j<4; j++ )
		{
			printf("%-2d\x20", *(p + i*4 +j));
		}
		printf("\n");
	}
	return 0;
}

要明白什么是指针,必须先要弄清楚数据在内存中是如何存储的,又是如何被读取的。 如果在程序中定义了一个变量,在对程序进行编译时,系统就会为这个变量分配内存单元。编译系统根据程序中定义的变量类型分配一定长度的空间。内存的基本单元是字节,一字节有8位。每字节都有一个编号,这个编号就是“地址”,它相当于旅馆的房间号。在地址所标示的内存单元中存放的数据,就相当于在该旅馆房间中居住的旅客。 大家一定要弄清楚“内存单元的地址”和“内存单元的内容”这两个概念的区别,即“房间号”和“房间内所住客人”的区别。在程序中一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。这种按变量地址存取变量的方式称为直接访问方式。 还有一种间接访问的方式,即变量中存放的是另一个变量的地址。也就是说,变量中存放的不是数据,而是数据的地址。就跟寻宝一样,可能你按藏宝图千辛万苦找到的宝藏不是金银珠宝,而是另一张藏宝图。按C语言的规定,可以在程序中定义整型变量、实型变量、字符型变量,也可以定义这样一种特殊的变量,它是存放地址的。 由于通过地址能找到所需的变量单元,所以可以说,地址“指向”该变量单元。如同一个房间号指向某一个房间一样,只要告诉房间号就能找到房间的位置。因此在C语言中,将地址形象地称为“指针”,意思就是通过它能找到以它为地址的内存单元。 所以,一个变量的地址就称为该变量的指针指针就是地址,而地址就是内存单元的编号。它是一个从零开始的、操作受限的非负整数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值