指针学习笔记

一、基础内容与提醒

1、基础内容

内存地址、首地址、存储空间大小

要想学好指针,我们首先要明白什么是内存地址。

我们知道计算机通过晶体管的开关状态来记录数据。它们以八个为一组,我们称之为字节。计算机上有许多组这样的晶体管,我们将每组晶体管,即每个字节看作成一个“房间”,每个房间则有八个二进制位。

每个”房间“从零开始,都有对应的房号,这些房号就是我们所说的内存地址

拿 int 数据类型举例,假设其房号为301,因为它占据了四个字节,所以它要住四个房间,即

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rRA5JbvS-1666702537859)(https://gitee.com/Kivykiwi/66666/raw/master/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20221022161048.jpg)]

我们把该数据对象的第一个房间号称作其首地址,它需要的房间数量即为存储空间大小

这也告诉我们,记录数据对象在内存中的位置时,需要两个信息:

a.数据的首地址

b.数据的存储空间大小

2、提醒

为了能让指针这一内容学起来更加轻松,现在请牢记数据类型这一个关键词,后续内容的分析离不开对数据类型的清晰认识。

例如:

int a 的数据类型为 int ;

int b[10] 的数据类型为 int[10] 。

二、指针

1.取地址运算符&

作用:写在数据对象左边,可以获取数据对象的首地址和所需存储空间大小。

例子

int n;

类型 pn = &n;

则变量pn储存了n的首地址与其所需的空间大小,那么变量pn的类型又是什么呢?先留个悬念。

2.声明指针类型的变量与取值运算符*

指针的定义:设一个数据对象为 x ,另一个数据对象为 p 。p 存储了 x 的首地址和所占空间大小。那么, p 称之为 x 的指针,或者是 p 指向 x

声明指针的公式

目标类型 * 变量名

如 int * pn

因此,我们可以知道,上述的变量pn 的类型为 int * 。

取值运算符*:将 * 写在一个指针的左边吧,可以根据指针中存储的首地址和空间大小找到目标数据对象。

接下来看下列代码:

#include <stdio.h>
int main()
{
    int n=19;
    int* pn = &n;
    
    printf("&n=%u",&n);
    printf("pn=%u",pn);
    printf("*pn=%d",*pn);
    printf("sizeof pn = %d",sizeof(pn));
    
    return 0;
}    

结果:&n=6684180; pn=6684180; *pn=19 sizeof(pn)=8

下列是知识点的灌输:

a. pn 的值,就是目标数据对象的首地址,即 n 的首地址,而目标数据对象 n 所需空间大小则与指针类型有关,类型 int* 标记变量 n 占用 sizeof(int) 字节,即4个字节。

b. *pn 的值,即根据 pn 中的首地址与大小找到的数据对象的值,即 n 的值。

c. %u则用于输出目标数据对象的地址。

d. 指针类型的大小,即 sizeof(pn) 的值,有编译器或编译配置决定,指针类型在32位程序中占4字节,在64位程序中占8字节。

3.强制转换指针类型

指针类型可以强制转换,转换后,其首地址与存储空间大小也将发生改变

int n = 123;
int* pn = &n;
char* pc = (char*)pn;

printf("pc=%u,pn=%u",pc,pn);
printf("*pc=%d,*pn=%d",*pc, *pn);

结果:pc=6684180,pn=6684180; *pc=123, *pn=123;

由上述结果得,pn的类型由 int* 转换为 char* ,赋值后,pn 与 pc 均存储了 n 的首地址,均打印了同样的地址,均打印了 n 的值,所以强制转换成功。

注:上述转换是将 int* 转换为 char* , pc 的存储空间大小为1个字节,因此,如果n 的值大于127,如1234,由于 *pn 的数据类型为4个字节的 int , *pn 的值也为1234,但数据类型大小为1个字节的 *pc 的值则会变为没有意义与逻辑的数字。

4.通过指针修改目标数据对象变量的值

假设变量 n 的值为100 ,存储在了6684180的这个地址上,其指针为 pn , 则 *pn 的值也为100 ,但当我们将 *pn 的值修改为1000时,变量 n 的值也会变为1000 ;

int n = 100;
int* pn = &n;

printf("n=%d",n);

*pn = 1000;

printf("n=%d",n);

结果:n=100; n=1000

即通过指针可以修改目标数据对象变量的值。

三、指针运算

1.指针的赋值

由于指针包含了数据对象的首地址与空间大小这两种信息,因此,我们在对指针赋值的时候不能只用数字进行赋值,如:int* pn = 100;这样是错误的,我们因该将要赋予指针的值转换为与指针同类型的样式,才可完成赋值,如:int* pn = (int*)100;

2.指针类型与整型加减

公式:指针加减 n 后首地址将向前或后移动 n * 步长 个字节,其中步长为sizeof(目标数据对象)。

例子:

#include <stdio.h>
int main()
{
    char* pc = (char*)100;
    int* pn = (int*)100;
    int* ps = (int*)100;
    
    pc = pc + 1;
    pn = pn + 1;
    ps = ps - 1;
    
    printf("pc=%u",pc);
    printf("pn=%u",pn);
    printf("ps=%u",ps);
    
    return 0;
}

结果:pc=101; pn=104; ps=96;

3.同类型指针减法运算

指针类型与指针类型相减后,其结果为两首地址差除以步长。

例子:

#include <stdio.h>
int main()
{
    int* pn = (int*)100;
    int* pc = (int*)120;
    
    printf("%d",pc-pn);
    return 0;
}

结果: 5

另外,由于指针类型与指针类型相加、相乘、相除没有实际意义,C语言中无法通过编译。

四、多级指针

开始之前,先回想之前的提醒,注意类型!因为如果内容要通过编译,等号两边的类型必须一致。而从这一节开始,我们对类型的灵敏度要越来越高,这样学起来才不会乱。

1.指针的指针

看下列代码:

#include <stdio.h>
int main()
{
    int n = 123;
    int* pn = &n;
    int** pnn = &pn;
    
    printf("%d",**pnn);
    return 0;
}

结果; 123

我们接下来分析上述代码并灌输知识

a. pnn 是 pn 这一指针的指针,获取了指针 pn 的首地址及其空间大小。

b. pnn 的类型为 int** , 是一个二级指针。

c. 为什么 **pnn 的值为123呢,我们看看其取值过程。首先,对pnn使用取值运算符,将int****还原为int * 类型,接着对 *pnn 使用取值运算符,将 int * 还原为 int 类型。即,还原为 n .

通俗点讲,这其实就是一条有指向的链子,无论有多少个指针,只要指针与指针之间,指针与整形之间相互连接,都可以通过分析其类型来找到一一对应的关系,所以弄清楚类型是特别重要的方法。

2.多级指针

直接上代码:

#include <stdio.h>
int main()
{
    int n = 100;
    int* one = &n;
    int** two = &one;
    int*** three = &two;
    int**** four = &three;
    int***** five = &four;
    
    printf("%d",*****five);
    return 0;
}

结果:100

five 的类型为 int* * * * * ,对其使用五次取值运算符将其还原为 int 类型,即 n 的值。

五、指针与数组

1.用指针访问数组

既然指针有访问首地址的功能,那么其是否可以逐一访问数组的每一个元素呢?

请看下列代码:

#include <stdio.h>
int main()
{
    int a[3] = {1,2,3,};
    
    int* p = &a[0];
    
    printf("%d",*p);
    printf("%d",*(p+1));
    printf("%d",*(p+2));
    
    return 0;
}

结果; 1; 2; 3;

分析:a是一个 int 类型的数组,每一个元素占四个字节,p 是 a[0] 这一元素的指针,指向 a[0] ,*p的值即为 a[0] 的值,p+1 则在 p 的基础上向后移动4个字节,正好指向 a[1] 的首地址,所以 p+1 是 a[1] 的指针,p+1 的值即为 a[1] 的值,以此类推。

注意:表达式 p+1 必须先被括号包裹,再使用取值运算符*。这是因为取值运算符 * 的优先级高于算术运算符,我们需要先让首地址移动,再进行取值操作。

2.使用数组名获取数组地址

使用数组名获取数组首地址能显得更加方便。

看下列代码:

#include <stdio.h>
int main()
{
    int a[3] = {1,2,3};
    int* p = a;
    
    printf("a=%u",a);
    printf("&a[0]=%u",&a[0]);
    printf("p=%u",p);
    return 0;
}

结果:a=5201314; &a[0]=5201314; p=5201314;

分析:首先 a 的值与 &a[0] 的值相等,都表示首地址,其次, a可以将值赋予给指针 p ,因此我们可以得知,a 的类型为 int* ,是一个指针,与 &a[0] 有着相同的作用。

知识灌输

a. 当数组名 a 出现在一个表达式当中,数组名 a 将会被转换为指向数组第一个元素的指针。不过有两个例外:

对数组名使用 sizeof 时对数组名使用 & 时

3.使用指针访问数组等价于下标访问

a. 中括号 [ ] ,被称作下标运算符,它的优先级高于一切其他运算符。

b. 数组名[下标] 等价于 *(数组名 + 偏移量)

​ 例如: a[1] == *(a+1)

六、指针数组

1.指针与指针数组的关系

a. int* pn 是一个指针,类型为 int* , int* pc[3] 是一个指针数组,类型为 int* [3] 。

b. 指针数组是一个存放着指针的数组

2.指针数组实例展示

看下列代码:

#include <stdio.h>
int main()
{
    int n1[3] = {1,2,3};
    int n2[3] = {11,22,33};
    int n3[3] = {111,222,333};   //对三个数组进行命名
    
    int* pn[3];  //定义了一个含有三个元素的指针数组
    
    pn[0] = n1;  //指针数组第一个元素是 pn[0] ,是一个指针,获取了数组 n1 的首地址
    pn[1] = n2;  //指针数组第二个元素是 pn[1] ,是一个指针,获取了数组 n2 的首地址
    pn[2] = n3;  //指针数组第三个元素是 pn[2] ,是一个指针,获取了数组 n3 的首地址
    
    for(int i = 0 ; i<3 ; i++)
    {
        int** p = pn + i;  //pn 是一个数组,在表达式中转换为指针,类型是 int** ,p 指向了 pn + i 的首地址
        for(int j = 0 ; j<3 ; j++)
        {
            printf("%d ",*(*p+j));  //*p 类型为 int* ,是一个指针类型,当i=0时,*p+j 指向数组 n1 中 n1[j] 的首地址,*(*p+j) 的值则是 n1[j] 的值
        }   
        printf("\n");
    }
    return 0;
}

结果: 1 2 3

​ 11 22 33

​ 111 222 333

通过指针数组,我们便可以访问多个数组的每一个元素了。

七、数组指针

1.数组指针的形式

定义一个二维数组 int b[3] [4] , 则 b 的类型为 int[3] [4] , 如果定义指针 pb 指向数 b ,那么定义如下

**int (*pb)[4] = b **

注:括号的用途是防止数组指针变为指针数组。

2.数组指针的移动与取值

看下列代码:

#include <stdio.h>
int main()
{
    int b[3][4] = {
        {0,1,2,3},
        {4,5,6,7},
        {8,9,10,11},
        {12,13,14,15},
    };
    
    int(*pb)[4]=b;  //pb 是二维数组b的指针,*pb是一个数组,pb+1 移动了 4*4=16 个字节。
    
    int* p = *pb  //此时 p 是数组 *pb 的指针,p+1 移动4个字节。
    
    printf("%d",*(pb));
    printf("%d",*(pb+1));
    printf("%d",*(pb+2));  //一行一行地访问
        
    printf("%d",*p);    
    printf("%d",*(p+1));   
    printf("%d",*(p+2));
    printf("%d",*(p+3));  //一个一个的元素进行访问
    
}

结果:0; 4; 8; 0; 1; 2; 3;

3.指针访问与下标访问等价

*指针[下标] = (指针 + 偏移量)

即 上述 *(pb+1) 可写成 pb[1]

八、声名顺序

对于数组、函数、指针,它们遵循下列的运算数序,优先级从高到低:

1、括号()

2、函数声明的( ) 与数组声明的()优先级相同

3、指针 声明的*

九、指针作为参数传递

1.指针能跨函数传递

由于被调函数的形参无法改变主调函数的变量,我们可以通过指针来进行数据传输。

看下列代码:

#include <stdio.h>
void swap(int* x , int* y)
{
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main()
{
    int a,b;
    a = 1;
    b = 2;
    swap(&a,&b);
    
    printf("a=%d , b=%d",a,b);
    return 0;
    
}

结果: a=2 , b=1;

因此,可以通过指针来传递参数并改变主调函数的变量。

2.仅有首地址的指针类型void*

性质:仅保存首地址,不保存目标的空间大小,不能进行取值与加减运算。但任意类型的指针都可以直接赋值给它。

十、函数与指针

1.从函数中返回指针

看下列代码:

#include <stdio.h>
int* fun()  //返回的是指针,函数类型也是指针
{
    static int n = 100;  //static 保证 n 在第一次被使用完后不被回收
    n++
    return &n;
}

int main()
{
    int* p = fun();  
    printf("%d",*p);
    fun();  //再次调用被调函数
    printf("%d",*p);
    
    return 0;
}

结果:100; 101;

2.初始化指针并从函数中返回多个变量

看下列代码:

#include <stdio.h>
void func(int**a,int**b)
{
    static int x = 100;
    static int y = 200;
    *a = &x;
    *b = &y;
}

int main()
{
    int* a = NULL;  //NULL 可以初始化指针,将指针内保存的地址设置为0
    int* a = NULL;
    func(&a,&b);
    printf("a=%d,b=%d",*a,*b);  //将指针的指针传入被调函数
    return 0;
}

结果:*a=100; *b=200;

十一、函数指针

1.使用指向函数的指针

看下列代码:

#include <stdio.h>

int print(char*pc)
{
	int count=0;
	while(*pc!='\0')  //刚开始 *pc 为 h 
	{
		putchar(*pc);
		pc++;
		count++;
	}
	putchar('\n');
	return count;
}

int main()
{
    int(*p)(char*)=print;  //函数的指针的写法
    int n = (*p)("helloworld");
    printf("%d",n);
    
    
	return 0;
}

结果:helloworld

​ 10;

十二、字符串与字符指针

1.字符数组与指针

看下列代码:

#include <stdio.h>
int main()
{
    char str[] = "alniubi";
    char* p = str;
    while(*p != '\0')
    {
        *p-=32;
        p++;
    }
    puts(str);
    return 0;
}

结果:ALNIUBI;

用指针指向字符串和数组类似

十三、const关键词

1.const的作用

const 修饰了某个类型,则该类型无法再被改变

2.const修饰指针本身

a. char const *str = “hello”;

b. const char *str = “hello”;

c. char * const str = “hello”;

对于以上三种表述,a 和 b 同类,const 在 * 左边修饰指针指向的数据 ,此时 str 可以修改,“hello” 不能修改。

c 则不同,const 修饰右边指针本身。

intf(“%d”,n);

return 0;

}


结果:helloworld

​            10;



## 十二、字符串与字符指针



### 1.字符数组与指针

看下列代码:

```c++
#include <stdio.h>
int main()
{
    char str[] = "alniubi";
    char* p = str;
    while(*p != '\0')
    {
        *p-=32;
        p++;
    }
    puts(str);
    return 0;
}

结果:ALNIUBI;

用指针指向字符串和数组类似

十三、const关键词

1.const的作用

const 修饰了某个类型,则该类型无法再被改变

2.const修饰指针本身

a. char const *str = “hello”;

b. const char *str = “hello”;

c. char * const str = “hello”;

对于以上三种表述,a 和 b 同类,const 在 * 左边修饰指针指向的数据 ,此时 str 可以修改,“hello” 不能修改。

c 则不同,const 修饰右边指针本身。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SZU_梁棋炜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值