【指针】
C语言使用数据名调用数据,数据名相当于C语言的直接寻址,直接寻址只能调用固定数据,而指针是间接寻址,指针存储了另一个数据的地址,使用指针调用数据时首先取指针存储的内存地址,之后使用此地址调用数据,使用间接寻址有如下几点优势:
1.统一数据的调用方式,因为指针是调用数据的中间层,修改指针即可调用不同的数据,当代码需要在不同情况下调用不同数据时,可以使用指针统一调用,只需要在不同情况下修改指针为不同的内存地址即可,无需编写多组代码分别调用这些数据,当数据需要在多处代码中同时调用、并在不同情况下同时修改时,使用指针的优势更明显,修改了指针就等于修改了所有使用指针调用数据的代码。
2.突破编译器限制,使用指针调用数据时编译器不会进行限制,只要操作系统不禁止即可,可以通过此特性绕过编译器的某些限制,比如在一个函数内调用另一个函数的局部数据。
3.接收未知的内存地址,比如向操作系统申请内存,比如调用动态链接库成员。
指针的长度在不同计算机中是不同的,在32位处理器程序中指针是无符号4字节整数,在64位处理器程序中指针是无符号8字节整数。
依据指向数据的类型可以将指针分为多种,指针用于存储哪种类型数据的地址就属于哪种类型的指针,编译器通过指针类型确定要操作多少个内存单元,int类型指针操作4个内存单元。
#include <stdio.h>
int main()
{
int a = 9;
int * p1 = &a; //int指定指针类型,*符号表示定义指针,p1为指针名,使用&符号提取一个数据的地址进行赋值,注意这里的&符号并非表示与运算
printf("变量a的值为:%d\n", *p1); //使用 *p1 调用指针指向的数据
printf("变量a的地址为:%p\n", p1); //使用 p1 调用指针本身
p1 = 0; //指针不再使用后修改为0,避免错误调用
return 0;
}
使用指针可以随意调用数据,数据调用方式更灵活,灵活的代价是容易出错,使用指针时应该做到代码严谨,同时定义指针暂时不使用时应该将其赋值为0,防止直接使用未赋值的指针,若指针占用的内存原有数据可以当做合规的内存地址使用,将会使用一个未知的内存地址,另外指针不再使用后也应该修改为0。
多重指针
指针也可以存储另一个指针的地址,相当于多层间接寻址。
#include <stdio.h>
int main()
{
int a = 5;
int * p1 = &a; //指针
int ** p2 = &p1; //指针的指针
int *** p3 = &p2; //三重指针,很少使用
printf("p2存储的数据为:%p\n"
"p2指向的数据为:%p\n"
"p2最终指向的数据为:%d\n",
p2, *p2, **p2);
return 0;
}
指针变量、指针常量
1.指针变量,指针本身是变量,赋值后可以修改,可以存储变量、常量的地址。
2.指针常量,指针本身是常量,赋值后不能修改,可以存储变量、常量的地址。
3.变量指针,指向变量的指针,存储变量的地址,可以通过指针修改指向的数据,不能存储常量的地址,但编译器默认只是给一个警告,不会禁止。
4.常量指针,指向常量的指针,存储常量的地址,不能通过指针修改指向的数据,也可以存储变量的地址,此时指针将变量认为是常量,并且不能通过指针修改它,若一个指针即需要指向变量也需要指向常量、同时不需要修改指向的数据,可以将其定义为常量指针。
#include <stdio.h>
int main()
{
int a = 0;
const int b = 9;
int * const p1 = &a; //const在指针名之前,定义指针常量
const int * p2 = &b; //const在类型名之前(或类型名与*符号之间),定义常量指针
const int * const p3 = &b; //组合使用
return 0;
}
指针作为数组元素
#include <stdio.h>
int main()
{
int i1=1, i2=2, i3=3, i4=4, i5=5;
int * p1[5] = {&i1, &i2, &i3, &i4, &i5}; //p1为存储指针的数组,元素为int类型指针
printf("%d\n", *p1[0]); //调用数组第一个元素指向的数据
char * p2[2] = {"阿狸", "喜羊羊"}; //可以使用数组为p2赋值,编译器自动取每个数组的地址作为p2元素的值
printf("%s\n%s\n", p2[0], p2[1]); //输出p2两个元素指向的字符串
return 0;
}
● restrict关键词
定义指针时添加restrict关键词表示代码可以保证指针指向的数据不会通过其他指针或者数据名进行修改,只会通过此指针修改,让编译器放心进行高效优化。
int * restrict p1;
【数组指针】
数组指针表示存储数组地址的指针,数组第一个元素的地址就是数组的地址,可以使用数组首元素的指针表示数组指针。
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int * p1 = &a[0];
for(int i = 0; i < 5; i++)
{
printf("%d\n", *p1);
p1++; //指针加1,指针记录的地址增加数据类型长度,这里的int类型指针会+4,定位到下一个元素
}
return 0;
}
数组指针可以使用下标的方式调用数组元素,原理与使用数组名相同,“指针地址+下标*数组元素长度”得出数组元素地址。
#include <stdio.h>
int main()
{
/* 单层指针 */
int a[5] = {1,2,3,4,5};
int * p1 = &a[0];
/* 遍历数组 */
for(int i = 0; i < 5; i++)
{
printf("%d\n", p1[i]); //将指针当做数组名使用,使用下标的方式调用数组元素
}
return 0;
}
数组指针还有另一种定义方式,它记录了数组长度信息,用于对指针赋值时进行检查,若赋值为长度不同的数组的地址则会发出警告,但默认不会禁止编译,这个长度信息只在编译器中记录,编译后的程序并没有记录此信息。
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int (*p1)[5] = &a; //p1为数组指针的名称,只能赋值为5个int类型元素数组的地址
printf("数组第一个元素的值:%d\n", (*p1)[0]); //调用指针指向的数据
printf("数组第一个元素的地址:%p\n", p1); //调用指针存储的地址
return 0;
}
【结构体指针】
#include <stdio.h>
int main()
{
struct zoo
{
char name[20];
int age;
} ali = {"阿狸", 8};
struct zoo * p1 = &ali; //定义结构体指针,只能赋值为同类型结构体实例的地址
printf("%s:%d岁\n", p1->name, p1->age); //使用->符号调用结构体成员
printf("%s:%d岁\n", (*p1).name, (*p1).age); //另一种使用方式
return 0;
}
共用体指针使用方式与结构体指针相同,不再重复介绍。
结构体成员指针
#include <stdio.h>
int main()
{
struct zoo
{
char name[20];
int age;
} ali = {"阿狸", 8};
char * p1 = &ali.name[0];
int * p2 = &ali.age;
printf("%s:%d岁\n", p1, *p2);
return 0;
}
【指针运算】
指针变量可以进行数学运算,不同类型的指针运算结果不同,运算规则如下:
1.单数据指针,指针+1,指针本身增加数据的长度,定位到下一个同类型数据,比如int类型指针+1等于指针值+4,减法同理。
2.数组指针,指针+1,指针本身增加数组元素的长度,定位到数组下一个元素。
3.结构体指针,指针+1,指针本身增加结构体的长度,注意结构体长度需要额外计入成员地址对齐占用的存储空间。
4.函数指针,不支持数学运算。
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int * p1 = &a[0];
/* 遍历数组 */
for(int i = 0; i < 5; i++)
{
printf("%d\n", *p1); //输出
p1++; //每次循环指针+1,定位到下一个元素
}
return 0;
}
数组元素指针相减
指向同一个数组不同元素的指针之间可以进行减法运算,编译器会转换为计算两个指针指向元素的下标相减。
#include <stdio.h>
int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int * p1 = &a[5];
int * p2 = &a[8];
printf("%lu\n", p2 - p1); //p2-p1 = 8-5,输出3
return 0;
}
【指针类型转换】
指针类型可以使用代码转换,编译器使用转换后的类型操作内存地址、进行指针运算,转换语法如下:(类型名*)指针名。
指针类型转换只存在于转换运算式中,不影响指针原来的类型,若需永久转换类型可以使用另一个指针接收转换后的指针。
指针类型可以随意转换,编译器不做限制,若一个未知数据需要通过指针逐个操作其中每个字节,可以将指针转换为char类型,之后即可按字节读写其中的数据。
#include <stdio.h>
int main()
{
int a = 257;
int *p1 = &a;
printf("%d\n", *(char*)p1); //int指针转换为char类型,只调用第一个字节,257转二进制 = 0001 0000 0001,低8位为1,输出1
return 0;
}
结构体指针类型转换
#include <stdio.h>
int main()
{
struct k1
{
char c[4];
//......
} ali = {"ali"};
struct k2
{
int i;
//......
};
struct k1 * p1 = &ali;
struct k2 * p2 = (struct k2 *)p1; //p1转换为k2类型
printf("0x%x\n", p2->i);
return 0;
}
空类型指针
指针类型可以定义为void,表示类型为空,空类型指针可以存储任何类型数据的地址。
空类型指针不能直接使用,需要首先转换为具体的类型再使用,否则编译器无法确定要操作多少内存单元。
当你需要接收一个指针但又不确定它的类型时,可以定义一个空类型指针接收,之后再转换为具体类型使用。
#include <stdio.h>
int main()
{
int a = 9;
int * p1 = &a;
void * p2 = p1; //空类型指针
//printf("%d\n", *p2); //错误,不能使用
printf("%d\n", *(int *)p2); //正确,转换为int类型再使用
return 0;
}
标签:p2,p1,06,int,C语言,调用,数组,指针 From: https://www.cnblogs.com/alixyy/p/18173710