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