目录

一、普通指针(一级)

1.定义

2.指针变量和普通变量的异同点

3.使用:​​​​​

4.指针字节大小

5.习题练习

总结

6.指针的好处

总结

7.指针与引用

8.指针与const

Q&A

二、二级指针


一、普通指针(一级)

前言:

大多数现代计算机都将内存分割为字节(byte),每个字节可以存储8位的信息。每个字节都有唯一的地址,用来和内存中其它字节相区别。

可执行程序由代码(原始C语言中与语句对应的机器指令)和数据(原始程序中的变量)两部分构成。程序中的每个变量占有一个或多个字节内存,把第一个字节的地址称为变量的地址,这就是指针的出处。

1.定义

  • 指针变量的声明:和普通变量基本一样,唯一的不同是在指针变量名字前放星号*:int *p;
  • 取地址运算符:为了找到变量的地址,可以用取地址运算符&,如果x是变量,&x就是x在内存中的地址。& 变量名(获取该变量的地址) ,针对所有变量都适用。

    输出地址:printf(“%d”,&a); //%d 输出10进制 ,%p输出16进制,%0输出八进制

    补充:scanf里scanf("%d",&a);用&输入值也是一样的道理,通过地址才能在scanf函数中形参中把值带过来。如果int i,*p=&i; scanf("%d",p);此时如果再写&p就是错误的。

  • 间接寻址运算符:如果p是指针,*p就是p当前指向的对象。
  • 指针变量初始化:
    //1.可以在声明的同时对它初始化
    int i;
    int *p=&i;
    //2.i的声明可以和p的初始化一起
    int i,*p=&i;

2.指针变量和普通变量的异同点

  • 相同点:
  1. 都能改变
  2. 都有地址
  • 不同点:
  1. 指针变量可以解引用,普通变量不可以

3.使用:​​​​​

通过解引用修改变量的值

  • 只要p指向i,*p就是i的别名,*p不仅拥有和i相同的值,而且*p的改变也会改变i的值。
  • *+指针变量:解引用,间接访问符,*p就是对p解引用

例如:int* p=&a;

           *p=100;

p指向a,p格子内是a的地址。

*p是对a解引用,得到a格子内的值,也就是a的值,令*p=100,则a=100

补充,对*的理解

  • int *p=&a;中*不是间接寻址运算符,其作用是指明p类型是指针
  • *p=100; *作为解引用,执行间接寻址
  • *p=&a;是错误的,因为它把a的地址赋给了p所指向的对象,而不是p本身
  • int p;//定义整型变量
  • int* p; //定义整形地址变量
  • &a; //整型地址变量
  • p=&a;//整型地址变量p保存整形a地址值
    • int*p=&a;//把上面两步合并

4.指针字节大小

  • 与类型无关,由平台决定
  • X86:32位操作系统;2^32  一个字节8位,这种情况下,指针4个字节
  • x64:64位操作系统  2^68   这种情况下,指针8个字节
  • 一字节=-128到127   256  2^8
  • 1TB=1024GB;
    • 1GB=1024MB;    
  • 1MB=1024KB;    
  • 1KB=1024B;     
  • 1B=8b;

补充:

(1)Int*p1,p2,p2;

p1:指针变量,p2,p3:整型变量

(2)typedef  int* INT;

INT p1,p2,p3; //此时,三个均为指针变量,等价于Int*p1,*p2,*p2;

(3)#define INT int*

INT p1,p2,p3; //此时,仅第一个为指针变量,等价于Int*p1,p2,p2;

5.习题练习

eg1:

知识点:指针类型必须严格相等才能赋值(就是变量什么类型,指针什么类型)

char a;

short b;

int c;

unsigned long d;

double e;

float f;

(1)定义指针p1,保存a的地址

char* p1 = &a;

(2)定义指针p2,保存b的地址

short* p2 = &b;

(3)定义指针p3,保存c的地址

int* p2 = &c;

(4)定义指针p4,保存d的地址

unsigned long* p2 = &d;

(5)定义指针p5,保存e的地址

double* p5 = &e;

(6)定义指针p6,保存f的地址

float* p6 = &f;

eg2:

int a, b, c;

int* p1 = &a;

int* p2 = &b;

int* p3 = &c;

(1)通过p1,将a的值改为20,并输出;

*p1 = 20;

printf("%d", *p1);

(2)把p1的值赋给p2,通过p2修改a的值为30,并输出;

p2=p1; //注意p2=p1是使p2指向和p1同一块地址,*p2=*p1是把p1所指地址的值复制给p2。

*p2=30

printf("%d", *p2);

//知识点:任意数量的指针变量都可以指向同一个对象

(3)通过p1,p2,p3,将c的值改为a×b,并输出;

*p3 = *p2 * *p2;

printf("%d", *p3);

(4)定义指针p,指向a

int* p = &a;

(5)通过p1,将a的值改为10;

    *p1=10;

(6)定义指针p,将p指向b,并通过p将b的值改为20

int*p = &b;

*p = 20;

(8)定义指针p,通过p输出a,b的值(需要多条语句)

int*p;

p = &a; printf("%d", *p);//输出a的值;

p = &b; printf("%d", *p);//输出b的值;

eg3:问输出结果为?

void main()
{
    char a[]={1,1,1,1};
    int *p=(int*)a;
    printf("*p=%d\n",*p);
}

解:

  • (int*) :强转
  • *p: 从p所指向的首地址开始,以它基类型(基类型:当前指针变量所指向的变量类型)所指的首地址为偏移量
    • int 4字节:取4个地址
    • 题中假设a[0]地址为Ox2000,那么就是以Ox2000开始偏移4个
    • a[0]=a[1]=a[2]=a[3]=a[4]=00000001
    • 偏移4个就是1000000010000000100000001 
  • %d 十进制整数形式
    • %O 八进制
    • %X 十六进制
    • 输出结果为%d形式,也就是将1000000010000000100000001 用十进制转换输出

总结

 指针的使用

  1. 指针类型必须严格相等才能赋值(就是变量什么类型,指针什么类型)
  2. 任意数量的指针变量都可以指向同一个对象。p2=p1是使p2指向和p1同一块地址,*p2=*p1是把p1所指向地址的值复制给p2。
  3. *p: 从p所指向的首地址开始,以它基类型(基类型:当前指针变量所指向的变量类型)所指的首地址为偏移量

6.指针的好处

(1)一般变量想要带回值,只能用return带回一个值,而指针可以通过形参带回多个值。

 eg:

  • eg1:

主函数调用MyScanf(),将实参x,y传给形参a,b,形参类型是指针类型,可以将1,0带回给主函数:x=1,y=0。

void MyScanf(int* a, int* b)
{
    *a = 1;
    *b = 0;
}

int main()
{
    int x ;
    int y ;
    MyScanf(& x, & y);
    return 0;
}
  • eg2:

主函数调用Operate(),将1,2以及x,y传递给形参。形参接收到后,在函数内部实现a+b与a*b,因为参数类型是指针,所以在主函数内x=3,y=2。

int Operate(int a,int b,int* sum,int* mul)
{
        *sum=a+b;//主函数里把地址传过来,该函数内部对其解引用替换值
        *mul=a*b;
}

int main()
{
        int x,y;
        Operate(1,2,&x,&y);
}

(2)因为形参与实参的地址不一样,形参是局部变量,无法通过形参直接修改实参值,需要地址改变,指针可以通过调用函数改变实参的值。

例: 经典不同参数的交换函数

void Swap1(int a,int b)
{
        int temp=a;
        a = b;
        b = temp;
}
void Swap2(int *a,int* b)
{
        int *temp=a;
        a = b;
        b = temp;
}

void Swap3(int *a,int* b)
{
        int temp=*a;
        *a = *b;
        *b = temp;
}
void Swap4(int &a,int& b)
{
        int temp=a;
        a = b;
        b = temp;
}

//如下Swap函数错误:对野指针进行解引用

void Swap(int *a,int* b)
{
        int *temp=*a;
        *a = *b;
        *b = *temp;
}



  • 参数传递 
  • (a+b)+abs(a-b)/2   =>得到a和b中大的数
  • swap1:
    • 传的是值,值传递是拷贝,并 未发生交换现象
  • swap2:
    • swap2内的交换了,但是主函数的不变
    • 以水杯和水杯内的水果举例,a是水杯,是地址,*a是水果,是地址所指向的内容。在swap2中,传的是地址,交换的是指针的指向(主函数把水杯给swap2函数,swap2交换的是水杯,不是水杯内的水果)
  • swap3:
    • *a是值,是a所指向空间内的内容,a是指针
    • swap3和主函数内的值均改变了
  • swap4:
    • 引用在主函数时是(x,y),不写&
    • swap4和主函数内的值均改变了
  • 总结:
    • 当形参和实参是同一地址,修改里面的内容就要改变
    • 是同一空间,修改的内容不会改变

总结

指针的好处:

1.可以通过形参带回多个值

2.可以通过形参改变实参的值

7.指针与引用

引用与指针有什么区别?

  1. 引用必须被初始化,指针不必。引用相当于是给变量起别名,别名确定后不会修改
    int x=10;
    int y=20;
    int& z=x;
    z=y; //x=20
  2. 引用初始化以后不能被改变,指针可以改变所指的对象。
  3. 指针可以改变指向,引用不可以。
  4. 不存在指向空值的引用,但是存在指向空值的指针。
  5. 引用只能是一级的,指针可以多级。
  6. sizeof(指针)的大小是固定的,根据平台决定指针的大小,而sizeof(引用)是变量的大小

8.指针与const

const保护参数

        当看到fun(&x);语句时,通常会认为调用指针来修改参数的值,但是有时候fun也只是用来检查x的值而不是修改,因为传递指针可以提高效率——如果需要大量的存储空间,传递变量的值可能会浪费时间和空间。因此,在这种不需要修改指针值的情况下,可以用const进行保护,const应该放在形参的声明中,后面紧跟形参的类型说明

       

 void fun(const int *p){...}

        值得注意的是,这样的声明会被错误理解为函数fun不能修改p,但是实际是不能修改指针类型执行的整数,但是并不阻止fun修改p本身

Q&A

  • Q:指针总和地址一样吗
  • A:通常是,但不总是。在一些计算机上,指针可能是“偏移量”而不完全是地址。
  • Q:如果指针可以指向程序中的数据,那么使指针只想程序中的代码,是否有可能?
  • A:可能,函数指针。


二、二级指针

举例说明:

例:int a=10;

int *p=&a; //p指向a,(p格子内)保存a的地址

int **pp=&p; //pp指向p,(pp格子内)保存p的地址

“*”:解引用(格子箭头往前指)

pp:pp格子=200;

*pp:p的值=100 //1个“*”,箭头往前走1下。*pp是对p解引用得到p格子内的值,也就是100;

**pp:a的值=10 //2个“*”,箭头往前走2下。**pp是对a解引用得到a格子内的值,也就是10;

用法:

改变pp的指向,使其指向q:pp=&q;

通过二级指针pp修改a的值:**pp=200;

总结:

一级指针关联变量的地址,

二级指针关联一级指针的地址。

补充:

&&a错误

*&a=a; 对的,对a取地址又对其解引用

&*a错误,不能对a解引用

例题分析

eg1:

int a = 10;

int b = 20;

int* p;

p = &a;

*p = 30; 

int** pp = &p; 

(1)a,和**p的值为多少?

a=30; //p指向a,*p = 30修改了a的值;

**pp = 30;//解两次引用,箭头跳两次指向a,a格子内值为30

eg2:

int a = 10;

int b = 20;

int* p= &a;

*p=100;

int** pp = &p; 

 *pp = &b;

**pp =1000

int ***ppp =&pp;

***ppp=1;

(2)a和b的值为多少?

*p=100; 将a的值改为100

 *pp = &b; 将p的指向修改为b

**pp =1000;解2次引用,将b的值修改为1000

int ***ppp =&pp; 定义ppp指向pp

***ppp=1;将b的值修改为1

所以,a=100,b=1

补充:对野指针的理解

int *p=&a

printf(“%d”,*p); //可以

但是

p=NULL;

printf(“%d”,*p);//不行,不能对空进行解引用

但是,如下情况:

定义Fun(p)函数,在内部对p置空,调用Fun(p),再输出printf(“%d”,*p)

void Fun(int* p)

{

    p = NULL;

}

int main()

{

    int a = 10;

    int* p = &a;

    Fun(p);

    printf("d\n", *p);

    return 0;

}

//此时发现Fun函数并没有起作用,因为:A函数调用B函数,如果需要通过修改A中实参的值,则必须传指针,在B中再解引用

//fun函数未被执行,想要通过另一个修改其值,必须传指针,函数内部解引用(加*)

//如下所示,会导致错误

void Fun(int** p)

{

    **p = NULL;

}

总结:

NULL空指针,当前为无效指针,相当于告诉用户该指针作废

野指针:没有访问权限,指针定义时未初始化,易产生野指针

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐