C语言指针

指针

指针就是内存地址,指针变量就是存储地址的变量.

作用: 使用指针可提高程序的编译效率和执行速度,是程序更加简洁;通过传递指针参数,使被调用函数可向主调函数返回除正常的返回值之外的其他数据,从而达到两者之间的双向通信;还有一些任务,如动态内存分配,没有指针是无法执行的;指针还用于表示和实现各种复杂的存储结构(如链表),从而为编写出更高质量的程序奠定基础;利用指针可以直接操纵内存地址,从而可以完成和汇编语言类似的工作.

c语言提供两种指针运算符: * 和 & .

指针变量的定义语法

指针变量的定义语法: 数据类型 * 变量名 [ = 初值 ];
指针变量定义时,数据类型并不是指指针变量的数据类型,而是其所指目标对象的数据类型.例如: int* p ;定义 p 是指针变量,可以存储 int 型变量的地址,p 变量的类型是 int*,而不是 int.这是告诉编译器, p 变量只能存储整型变量的空间地址,不能存储其他类型空间的地址

取地址运算符:&

p = &a; 表明得到整型变量 a 的地址,并把该地址存入指针变量 p 中.通过这个赋值语句,实际上是让 p 变量指向 a 变量,通过 p 值可以找到 a 变量.

间接运算符:*

星号( * )如果不是在指针变量定义时出现,而是在某条语句的某个指针变量前出现,那么这个星号( * )就是间接运算符,即取指针所指向变量的值.

简单的定义指针和输出地址:

#include <stdio.h>
int main(void) {
	int a = 1;
	int* p;//定义指针;
	p = &a;//&a取a的地址;
	printf("地址为:%x",p);//输出a的地址;
	printf("\n*p的值为:%d", *p);//*p 就是p指针所指向a的值;
	return 0;
}

结果:
在这里插入图片描述

空指针

空指针常量是一个值为0的整数常量表达式,在头文件stdlib.h以及其他头文件中,宏NULL被定义为空指针常量.在定义指针常量时,指针的值可以初始化为NULL.例如:int*p = NULL;空指针时其值为NULL的指针,所以p是空指针.空指针不指向任何空间,不能用间接运算符*取值.所以编程时不要出现下面语句,如下所示:

#include <stdio.h>
int main(void) {
	int* ptr = NULL;//定义空指针
	int x = *ptr;//这里的ptr指针是空指针,所以不能使用间接运算符(*)取值,没有本语句,程序运行不会报错,但是加上这句程序会有问题	
	return 0;
}

这个肯定是会报错,是因为我们对空指针使用间接运算符,出现错误.
结果如下图所示:
在这里插入图片描述
这是我们要注意的一个点.

定义变量时未初始化的指针,或指向目标已销毁的指针称为悬浮指针,在定义指针变量时应该避免使用悬浮指针.

void指针

指向void的指针,简称void指针.void* 被称为万能指针.void指针指向一块内存,却没有告诉程序该用何种方式来解释这块内存.因此,不能用这种类型的指针直接获取所指内存的内容,必须先转成合适的具体类型的指针才行.

若想声明一个可以接收任何类型指针参数的函数,可以将所需的参数设定为void*指针。

比如标准函数memset(),它被声明在头文件string.h中,其原型如下:
void* memset( void *s, int c, size_t n );

#include <stdio.h>
#include <string.h>

int main(void) {	
	//void指针;
	//标准函数memset()原型:void* memset(void* s, int c, size_t n);
	int c[10] = { 1,2,3,4,5,6,7,8,9,10 };
	for(int i = 0;i< 10;i++)
		printf("%d ", c[i]);
	printf("\n");
	memset(c, 0, 10 * sizeof(int));
	for (int i = 0; i < 10; i++)
		printf("%d ", c[i]);
	return 0;
}

memset()函数调用将0值赋值到 c 数组的每个字节,实参 c 具有 int* 类型,在函数调用时,实参被转换为形参 void* ,这个类型转换是隐形的, memset() 函数常用来对数组元素清0.
结果如图所示:
在这里插入图片描述

malloc函数

malloc全称为memory allocation.中文是动态内存分配,用于申请一块连续的指定大小的内存块区域,以void斜体样式类型返回的内存区域地址.以 void 类型返回分配的内存区域地址,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态分配的内容.

void*类型可以通过类型转换强制转换为任何其他类型的指针.

malloc函数的原型: void* malloc (size_t size);
如果分配成功,则返回指向被分配内存的地址(此存储区中的初始值不确定),否则返回空指针NULL.
当内存不再使用时,应使用free()函数将内存块释放。free()函数的原型: void free (void* ptr);

下面我们来看一个例子.
随机生成指定长度字符串:

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

int main(void) {
	//生成随机指定长度字符串
	int n;//随机长度
	printf("请输入指定长度:");
	scanf_s("%d", &n);
	char *buffer = NULL;//定义指针变量并赋值为空指针;
	srand(time(0));//设置随机数种子;

	buffer = (char * )malloc(n + 1);//动态分配n+1个字节空间;
	if (buffer == NULL) exit(1);

	for (int i = 0; i < n; i++)
		buffer[i] = rand() % 26 + 'a';
	buffer[n] = '\0';

	printf("随机串:%s\n", buffer);
	free(buffer);

	return 0;
}

结果如图:
在这里插入图片描述

const 常量指针

在定义指针变量时用 const 关键字修饰,称为 const 指针常量,有以下几种情况:

常量指针

用 const 修饰 “*” 时称为常量指针.这样不能通过该指针变量修改指向的内容.
例如下面的程序:

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

int main(void) {
	//常量指针
	int a = 4,b = 5;
	const int* p = &a;//定义常量指针并初始化;
	//*p = 10;//*p其实就是a的值,也就是4,但是这个语句本来的意思是想将*p也就是a的值改为10,是不可以的.会出现编译错误.
	p = &b;//正确,因为p指针本身是一个变量,所以也可以指向其他整型变量;
	return 0;
}

程序中被注释掉的这一句是有问题的, * p 其实就是a的值,也就是4,但是这个语句本来的意思是想将*p也就是a的值改为10,但是因为p是常量指针,这样的操作是不被允许的,会出现编译错误.

常量指针变量

如果用 const 修饰指针变量名,称为常量指针变量.例如:

#include <stdio.h>
int main(void) {
	//常量指针变量
	int a = 5,b = 4;
	int *const p = &a;
	*p = 10;//通过p指针修改指向的数据;
	//p = &b;//本语句错误,不能修改p指针的值,因为p指针本身就是一个常量;
	return 0;
}

程序中被注释掉的语句是有错误的,这里的 p 指针是常量指针变量, p 的值不再发生变化,而且必须初始化,并且不能改变它的值.所以把 b 的地址赋给它,会出现编译错误.

指针常量

指针常量既是常量指针,也是常量指针变量.

#include <stdio.h>
int main(void) {
	int a = 1, b = 2;
	const int* const p = &a;
	//*p = 10;
	//p = &b;
}

因为指针常量既是常量指针,也是常量指针变量.所以常量指针和常量指针变量所不允许的,指针常量也不会被允许,所以上面程序被注释掉的两行会出编译错误.

指针与数组

通过指针变量访问数组

数组名是地址常量,是数组的起始地址,也是第一个元素的地址.由于数组元素在内存中的存储是连续存储的,且存放类型一致,因此计算机只需要知道数组的第一个元素的地址,就可以访问整个数组.
通过指针操作数组程序:

#include <stdio.h>
int main(void) {
	int a[4] = { 1,2,3,4 };
	int* p = a;//表示p值是a的值,a是数组的起始元素,因此p指向数组的第一个单元,也就是a[0];
	int i = 0;
	p[2] = 10;//等价于a[2] = 10;
	printf("a数组的首地址是:%p\n", a);
	for (i; i < 4; i++)
		printf("a[%d]的地址为:%p a[%d]的值为:%d\n", i, p + i, i ,* (p + i));
	return 0;
}

补充:%p是打印地址的, %x是以十六进制形式打印, 完全不同!另外在64位下结果会不一样, 所以打印指针老老实实用%p .(用%x其实也可以,但是结果有差别.)
结果如图:
在这里插入图片描述

数组指针

一维数组指针定义形式为:类型名 (*标识符)[数组长度]

数组指针访问二维数组程序:

#include <stdio.h>
int main(void) {
	//数组指针
	//int(*p)[10];//定义数组指针
	//int a[10];
	//p = &a;
	//printf("%p\n", p);

	/*int a[10];
	int(*p)[10] = &a;//p只能是10个整型空间的地址,不能是其它类型的地址,这里的数字必须相匹配,不一样都会报错;
	printf("%p\n", p);
	*/
	//上面两部分程序均可以使用;

	//数组指针访问二维数组
	int a[5][3] = {{1,2,3}};
	int(*p)[3] = a;//指向第一行;
	for (int i = 0; i < 5; i++) {
		for (int j = 0; j < 3; j++) 
			printf("%d   ", *(*p + j));
		p++;
	}
	return 0;
}

当一个指针指向一个普通数据变量时称为一级指针,指向一级指针的指针是二级指针,指向二级指针的是三级指针。例如:

#include <stdio.h>
int main(void) {
	int x = 5;
	int* p, ** pp, *** ppp;
	p = &x;
	pp = &p;
	ppp = &pp;
	return 0;
}

p是一级指针,pp是二级指针,ppp是三级指针。

指针数组

指针数组就是元素为指针类型的数组.

一维指针数组定义形式:类型名 *标识符[数组长度]
指针数组编程示例:

int* p[2] = { NULL,NULL };
	p[0] = (int*)malloc(7 * sizeof(int));//p[0]是一维动态数组(7个整型单元)的起始地址;
	p[1] = (int*)malloc(6 * sizeof(int));//p[1]是一维动态数组(6个整型单元)的起始地址;
	for (int i = 0; i < 2; i++)
		printf("%p\n", p[i]);

上面的代码是main()函数里面的,一定要有#include <stdlib.h>,没有这个,无法申请动态存储空间.

指向函数的指针(函数指针)

函数包括一系列指令,当它经过编译后,在内存中会占据一定的内存空间,该空间有一个首地址,指针变量可以存储这个地址,存储这个地址的变量就是函数指针(或者称为指向函数的指针).

#include <stdio.h>
int Add(int a, int b);
int main(void) {	
	 //函数指针,我们声明一个函数int Add(int a,int b);
	int m,n;
	printf("请输入两个整数: ");
	scanf_s("%d %d", &m, &n);

	int (*p)(int, int);//函数指针定义
	p = Add;
	//上面两句也可以这样写
	//int (*p)(int, int) = &Add;//直接在定义函数指针是初始化使它指向我们的Add()函数
	int result = p(m, n);
	printf("%d + %d = %d",m,n, result);
	return 0;
}
int Add(int a, int b) {
	int c = a + b;
	return c;
}

我们也可以直接在main()函数之前定义Add()函数,那就不用声明了,直接定义即可.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值