7-指针

117 阅读15分钟

知识点一:内存地址的概述

系统给内存的每一个字节 分配一个编号 而这个编号 就是内存地址

变量、函数、数组、字符串等开辟空间,开辟空间也会给予一个起始地址(内存地址)

内存地址

知识点二:指针变量

指针变量:本质就是一个变量,只是这个变量 存放 是内存的地址编号,指针变量也有起始地址,二级指针就可以保存一级指针,依次嵌套

在32位平台,指针变量任何类型的地址编号都是 4 字节

1:定义指针变量

*:在定义指针变量时,起到标识作用,其他地方表示获取一个指针变量保存的地址里面的内容,指针变量的解引用

&:取一个对应变量的地址,若对指针变量取地址,则是指针变量的地址(而不是指针变量指向的地址)

如果 & 和 * 同时存在 可以相互抵消

//形式:
    类型 *指针变量名

//p指向的类型为int => p保存int类型变量的地址
//指向......类型 == 保存......类型变量的地址
int num = 10;
int *p;
p = #	//p指向num ==p保存了num的地址
指针变量解析
void test02()
{
    //num拥有一个合法的空间
    int num = 10;
    //需求:请定义一个指针变量 保存num的地址
    //p就是指针变量 变量名为p 不是*p
    //在定义的时候:*修饰p 表示p为指针变量
    int *p;

    //建立p和num的关系:p保存num的地址
    printf("&num = %p\n", &num);	//&num = 0019FED8
    p = #	//&num 代表的是num变量起始地址(首地址)
    printf("p = %p\n", p);	//p = 0019FED8
}

2、指针变量使用

通过过 p 对所保存的地址空间进行读写操作

void test01 ()
{
    //num拥有一一个合法的空间 
    int num = 10;
    //需求:请定义一个指针变量保存num的地址
    //p就是指针变量变量名为p不是*p
    //在定义的时候:*修饰p表示p为指针变量
    int *p;
    
    p = #  //&num代表num变量起始地址(首地址)
    //指针变量指向变量p的起始地址
    printf("&num = %p\n",&num);    
    printf("p = %p\n",p);
    
    //*p 等价 num
    printf("*p = %d\n",*p);  //10 == num
    
    *p = 100;	//*p == 100 == num
    printf("num = %d\n",num);	//结果:num = 100
    
    scanf("%d",p);    //如果此处为&p表示键盘给p赋值而不是给num赋值
    printf("num = %d\n",num);
}

3、指针变量初始化

指针变量初始化为NULL

int *p = NULL;    //不要对p进行*p操作,容易出段错误

指针变量初始化为 合法空间

int num = 10;
int *p = #    //第一步定义指针变量int *p    第二步给指针变量赋值: p=&num 
void test01 ()
{
    int num = 10;
    int data = 100;
    
    //如果 局部指针变量 不初始化保存的是随机的地址编号(千万别取值)
    int *p;
    
    //不想让指针变量指向任何地方 应该初始化为NULL(千万别取值)
    int *pl = NULL;
    
    //将指针变量初始化为合法的地址(可以取值)
    //*修饰p2为指针变量,p2=#
    int *p2 = #		//第一步: int *p2;   第二步: p2=#
    printf("%d\n",*p2);	//num==10
    
    //指针变量p2本质是一个变量  可以更改指向
    p2 = &data;
    printf("%d\n",*p2);	//data==200
}

4、指针强制类型转换

//形式:
    (需要强转的类型 *)变量名;

//普通变量:
    (需要强转的类型)变量名;

5、指针变量取值宽度

宽度:指针变量指向的类型长度决定

指针变量取值宽度解析
#include<stdio.h>
int main(int argc,char *argv[])
{
    int  num = 0x01020304;
    int *p;
    short *p1;
    char *p2;
    p = &num;
    p1 = &num;
    p2 = &num; 
    
    printf("*p = %#x\n",*p);	//*p = 0x1020304
    printf("*p1 = %#x\n",*p1);	//*p1 = 0x0304
    printf("*p2 = %#x\n",*p2);	//*p2 = 0x04
}

6、指针变量的跨度

跨度:指针变量指向的类型长度决定

#include<stdio.h>
int main(int argc,char *argv[])
{
    int  num = 0x01020304;
    int *p;
    short *p2;
    char *p1;
    
    p2 = &num;
    p = &num;
    p1 = &num;
    
    printf("*p = %u\n",p);			//*p = 6356608
    printf("*p+1 = %u\n",p+1);		//*p+1 = 635612
    printf("*p1 = %u\n",p1);		//*p1 = 6356608
    printf("*p1+1 = %u\n",p1+1);	//*p1+1 = 6356609
    printf("*p2 = %u\n",p2);		//*p2 = 6356608
    printf("*p2+1 = %u\n",p2+1);	//*p2+1 = 6356610
}

例:int num = 0x01020304,自定义指针变量p 取出 0x0102

分析
int main(intargc, char*argv[])
{
    int num = 0x01020304;
    short *p;
    p = &num;
    printf("%#x\n", *(p+1));	//0102
}

//如果跨度和宽度不等时,需要类型转换
//选择min(跨度、宽度)定义指针变量
int main(intargc, char*argv[])
{
    int num = 0x01020304;
    char *p;
    short *p2;

    p = &num;
    p2 = &num;

    printf("%#x\n", *(short *)(p+1));	//0x0203
    printf("%#x\n", *(short *)((char *)p+1));	//0x0203
    return 0;
}

7、指针注意事项

void 不能定义变量

void num;    //错误的 系统不知道num的大小

void * 可以定义变量

void *p;    //p的类型为 void * 而void *指针类型32为平台4字节,系统知道给p开辟4字节
//p叫万能指针,p可以保存任意类型的一级指针

//对于 p 不能直接使用 *p 操作,必须实现对 p 进行强制类型转换
void test01 ()
{
    int num = 10;
    void *p;
    p = &num;
    //printf("%d\n", *p);    //错误:因为p的指向类型为void 系统确定不了宽度
    printf("%d\n", *(int *)p);    //p临时的指向类型为int 系统确定宽度4B
}

不要对没有初始化的指针变量 取*

int *p;
printf("*p = %d\n", *p);    //不能这样取*输出
//因为p没有初始化 内容随机 也就足p指向了一个未知空间系统不允许用户 取值*p操作

不要对初始化为 NULL 的指针变量 取*

//NULL就是(void *) 0 地址,也是内存的起始地址受系统保护
int *p = NULL;
printf("*p = %d\n",*p);    //也不能 *p

不要给指针变量赋普通的数值

int *p = 1000;    // 此时的1000对于p来说是地址编号1000
//*p表示在地址编号为1000的位置 取值,而地址编号1000不是合法的空间所以不能*p
printf("*p = %d\n",*p);    //也不能*p

指针变量不要操作越界的空间

char num=10;
int *p = &num;
//num只占1B空间,而p的指向类型为int 所以*p取值宽度为4B ,所以越界3B
printf("*p =%d\n", *p);    //操作非法空间

知识点三:数组元素指针

通过数组元素的指针变量 遍历 数组的元素

数组元素指针解析
void test05()
{
    int arr[5]={10,20,30,40,50};
    int n = sizeof(arr)/sizeof(arr[0]);
    int i=0;

    //p保存了 第0个元素的地址(首元素的地址)
    int *p = &arr[0];
    printf("*p = %d\n", *p);	//10

    p++;	//p=p+1
    printf("*p = %d\n", *p);	//20

    //前提是p保存的是第0个元素的地址
    p=&arr[0];
    for(i=0;i<n;i++)
    {
        //printf("%d ", arr[i]);
        //p+i代表的是第i个元素的地址
        //*(p+i)代表的是第i个元素的值
        printf("%d ", *(p+i));	//10 20 30 40 50
    }
    printf("\n");
}

通过数组元素的指针变量给数组的元素获取键盘输入

void test2()
{
    int i = 0;
    int num[5] = {0};
    int n = sizeof(num)/sizeof(num[0]);

    int *p = &num[0]; //或int *p = num;
    for(i = 0; i < n; i++)
    {
        //scanf("%d", &num[i]);
        scanf("%d",p + i);    //p + i == &num[i];
    }
    
    for(i=0;i<n;i++)
    {
    	printf("%d ", *(p+i));
    }
    printf("\n");
}

知识点四:数组的[]和*()的关系

数组名arr 作为类型,代表的是数组的总大小sizeof (arr)

数组名arr 作为地址,代表的是首元素地址(第0个元素的地址)

&arr[0] == &*(arr+0) == arr+0 == arr

在使用中本质:[] 是 *( ) 缩写

arr[1] 	*(arr + 1)	//缩写规则:+ 左边的值放在[]左边,+ 右边的值放在[]里面

知识点五:数组 arr 和 &arr区别

arr:数组的首元素地址,&arr:数组的首地址

数组名arr是一个符号常量,不能被赋值

区别分析
void test10()
{
    int arr[5]={10,20,30,40,50};
    //arr = 1000;	//err arr符号常量 不能被赋值
    //arr++;	//arr=arr+1; err
    arr+1;	//ok
}

知识点六:指向同一数组的两个元素指针变量间关系

void testll()
{
    int arr[5]={10, 20, 30, 40,50} ;
    int *p1 = arr;
    int *p2 = arr+3;
    //1、指向同一数组的两个指针变量相减,返回的是相差元素的个数
    printf("%d\n", p2 - p1);	//3
    
    //2、指向同一数组的两个指针变量可以比较大小
    if(p2 > p1)
    {
        printf(">\n");
    }    
    else
    {
        printf("<\n");
    }
    
    //3、 指向同一数组的两个指针变量 可以赋值
    p1 = p2;    //p1 和 p2指向同一处地址
    
    //4、指问同一数组的两个指针变量尽量不要相加
    printf("p2 = %u\n", p2);
    p1 + p2;    //错误:越界很厉害了
    
    //5、[]里面在不越界的情况下 可以为负数
    printf("%d\n", p2[-2]);	//20
}

知识点七:指针数组

指针数组:本质是数组,只是数组的每个元素是指针

//形式:
    类型 *数组名[元素个数];
指针数组解析
void test01()
{
    int num1 = 10;
    int num2 = 20;
    int num3 = 30;
    //指针数组
    int *arr[3] = {&num1, &num2, &num3};
    char *arr2[3];
    //arr[0] = &num1, arr[1]=&num2, arr[2]=&num3

    printf("%d\n", *arr[1]);	//*arr[1] == *&num2 == num2 == 20

    printf("%d\n",sizeof(arr));		//12
    printf("%d\n",sizeof(arr2));	//12
    return;
}

void test02()
{
    char *arr[3] = {"hehehe","hahaha","heiheihei"};
    printf("%d\n", sizeof(arr));	//12

    //%s输出是要求要字符串首元素地址,而arr[1]就是保存了字符串的首元素地址
    printf("%s\n", arr[1]);	//hahaha
    //arr[1]首元素地址(h) 再加一就是字符a的地址 再取值就是打印a
    printf("%c\n", *(arr[1]+1));	//a
}

知识点八:数组指针

本质是:指针变量,只是保存的是数组的首地址

//形式:
	指向的数组的类型 (*指针变量名)[指向的数组的元素个数];
数组指针分析
void test03( )
{
    int arr[5]={10,20,30, 40};
    int (*p)[5];	//数组指针: 本质是一个指针变量只是该变量保存的是数组的首地址
    printf("%d\n",sizeof(p));	//4 
    printf("p=  %u\n",p);			//p = 3612672
    printf("p+1 = %u\n",p+1);		//p+2 = 3612692
    
    p = &arr;    //&arr 才代表数组的首地址
    printf("%d\n", *(*p+3));	//40
    //*(*p+3) == *(*(p+0)+3) == *(p[0]+3) == p[0][3]      
    printf("%d\n", p[0][3]);	//40
}

知识点九:数组指针与二维数组的关系

数组指针与二维数组分析
void test03( )
{
    int arr[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int i,j = 0;
    int (*p)[4] = arr;
    for(i = 0;i<3;i++)
    {
        for(j = 0;j<4;j++)
        {
            //printf("%d\n",*(*(p+i)+j));
            printf("%d ",arr[i][j]);	//1 2 3 4 5 6 7 8 9 10 11 12
        }
        printf("\n");
    }
}

任何维度的数组在物理存储上都是一维

数组存储分析
void test05()
{
    int arr[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int i=0;
    int *p = &arr[0][0];
    for(i=0;i<3*4; i++)
    {
        //printf("%d ", *(p+i));
        printf("%d ", p[i]);	//1 2 3 4 5 6 7 8 9 10 11 12
    }
    printf("\n");
}

知识点十:多级指针

指针变量存放的是地址,但指针变量本身还有对应的地址,如果对指针变量取地址,就是指针变量本身的地址

多级指针分析

知识点十一:指针变量作函数参数

如果想在函数内部 修改 外部变量的值,需要将外部变量的 地址 传递给 函数(以指针变量作为函数的参数)

由于p1和p2分别保存了data1和data2的地址,可以通过p1和p2间接的修改data1和data2的值

指针变量作函数参数分析
#include <stdio.h>
void my_set_p(int **tmp_p)	//tmp_p = &p;
{
    static int num = 100;
    //*tmp_p == p
    *tmp_p = &num;	//p = & num;
}
int main(int argc,char *argv[])
{
    int *p = NULL;
    //在函数内部 更改p的指向(在函数内部 给p赋值 就必须传递p的地址)
    my_set_p(&p);
    printf("*p = %d\n", *p);	//100
	return 0;
}
int main(int argc,char *argv[])
{
	int a = 100;
	int b = 200;
	int *a1;
	int *b1;
	int *a2;
	int *b2;
	a1 = &a;
	b1 = &b;
	a2 = a1;
	b2 = b1;
    
    //情况一:指针变量相互赋值的话,只是地址转换,只能影响交换地址之间的值
	//a2 = b2;
	//printf("a2 = %d,b2 = %d\n",*a2,*b2);	//a2 = 200,b2 = 200
	//printf("a1 = %d,b1 = %d\n",*a1,*b1);	//a1 = 100,b1 = 200
    
    //情况二:交换的是 *变量,所对应的赋值的地址对应的值都会随着改变
	*a2 = *b2;
 	printf("a2 = %d,b2 = %d\n",*a2,*b2);	//a2 = 200,b2 = 200
	printf("a1 = %d,b1 = %d\n",*a1,*b1);	//a1 = 200,b1 = 200
	printf("a = %d,b = %d\n",a,b);			//a = 200,b = 200
    return 0;
}

知识点十二:一维数组名作函数参数

如果函数内部想操作(读、写)外部数组的元素,请将外部数组的数组名传递函数

一维数组作为函数形参会被优化成一级指针变量

#include <stdio.h>
//int my_input_array(int arr[5],int n)
//一维数组作为函数的形参会被优化成指针变量,arr[0] == *(arr + 0) == *arr
int my_input_array(int *arr, int n)
{
    printf("B:%d\n", sizeof(arr));    //4
    int i = 0;
    printf("请输入%d个int数据\n",n);
    for(i = 0; i < n; i++)
    {
        scanf("%d", arr+i);
    }
}
int my_print_array(int *arr, int n)
{
    int i = 0;
    for(i = 0;i<n;i++)
    {
        //printf("%d ", *(arr+i));
        printf("%d ", arr[i]);
    }
    printf("\n");
}
int main(int argc,char *argv[])
{
    int arr[5] = {0};
    int n = sizeof(arr)/sizeof(arr[0]);
    printf("A:%d\n", sizeof(arr));    //20
    
    //定义一个函数给arr获取键盘输入
    my_input_array(arr, n);
    
    //定义一个函数遍历数组元素
    my_print_array(arr, n);
    return 0;
}

知识点十三:二维数组名作函数参数

如果函数内部想操作(读、写)外部数组的元素,请将外部数组的数组名传递函数

二维数组名作为函数的形参会被优化成数组指针(参考数组指针解析)

int arr1[5] -----> int *p;
int arr2[3][4] ----> int (*p1)[4];
int arr3[3][4][5] ---> int (*p2)[4][5];
#include <stdio.h>
//void my_print_tow_array(int arr[3][4],int cow,int col)
//当二维数组作为函数形参会被优化成数组指针
void my_print_tow_array(int (*arr)[4], int cow, int col)
{
    int i,j = 0;
    printf("A = %d\n",sizeof(arr));
    for(i = 0; i < cow; i++)
    {
        for(j =0; j < col; j++)
        {
            printf("arr = %d ", arr[i][j]);
        }
        printf("\n");
    }
}
int main(int argc,char *argv[])
{
    int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    int cow = sizeof(arr)/sizeof(arr[0]);		//行数 
    int col = sizeof(arr[0])/sizeof(arr[0][0]);	//列数
    printf("A = %d\n",sizeof(arr));	//48
    my_print_tow_array(arr,cow,col);
    return 0;    
}

知识点十四:指针函数

该函数的返回值是一个指针,指针作为函数的返回值

//指针函数形式:
    函数返回值类型 *函数名(函数形参1, 函数形参2 .....)
#include <stdio.h>
int *get_addr(void)
{
    //int num = 1000;
    static int num = 1000;    //静态变量 程序结束前不会被释放
    return &num;    //不要返回普通局部变量地址(局部变量可能执行后会被释放而存放其他则返回的并不是原来的内容) 
}
int main(int argc,char *argv[])
{
    int *p = NULL;
    p = get_addr();
    printf("*p = %d\n",*p);
	return 0;
}

知识点十五:函数指针

函数名代表的是函数入口地址

//函数指针形式:
	函数返回值类型 (*变量名)(形参类型, 形参类型 .....)
#include <stdio.h>
int my_add(int a,int b)
{
    return a+b;
}
int main(int argc,char *argv[])
{
    //my_add代表的是函数的入口地址
    printf("%p\n", my_add);
    
    //函数指针本质:指针变量保存的是函数的入口地址
    int (*p)(int ,int ) = NULL;		//函数指针
    printf("%d\n",sizeof(p));
    
    //相当于函数指针指向函数地址 p == my_add
    p = my_add;
    printf("%p\n",p);
    
    //函数调用:函数入口地址+()
    printf("%d\n",my_add(10,20));	//30
    printf("%d\n",p(100,200));	//300
    //对函数指针变量取*无意义
    
    return 0;
}

知识点十六:函数指针作函数形参

#include<stdio.h>
int my_add(int a,int b)
{
    return a + b;
}
int my_sub(int a,int b)
{
    return a - b;
}
int my_mul(int a,int b)
{
    return a * b;
}

//定义一个函数实现,上述函数的功能
int my_calc(int a,int b,int ((*fun_pointer)(int ,int))    //将实参10,20传给形参a,b、函数地址(my_add)传给函数指针(fun_pointer)
{
    return fun_pointer(a,b);    //返回计算后的结果
}
int main(int argc,char *argv[])
{
    printf("%d\n",my_calc(10,20, my_add));    //30
    printf("%d\n",my_calc(10,20, my_sub));    //-10
    printf("%d\n",my_calc(10,20, my_mul));    //200
    return 0;
}