【C语言】指针(1)——指针变量的操作

摘要

       在计算机的底层逻辑中,CPU和内存之间通过数据总线来传输数据,把数值存储到地址中。我我们通过地址来获取数值可以加快我们程序的运行效率,而所用到的就是指针。

       指针是一种变量,通过存储地址,再通过解引用符来调用数据,根据调用方式的不同,分别为值传递,址传递。

 

目录

摘要

指针的应用

指针的底层原理(硬件方面)

指针变量和解引⽤操作符(*)

拆解指针类型

解引用操作符

指针变量的⼤⼩

指针变量类型的意义

指针+-整数

void*指针

指针运算

指针+- 整数

指针-指针

指针的关系运算

指针的使⽤和传址调⽤

strcat的模拟

传值调⽤和传址调⽤


 

 

指针的应用

       假设我们住在同一个大公寓中,每个房间都有一个房间号,如果要找到小明,那么我们总不可能说在这一大栋公寓说:“我们要找小明”,很显然,这是吃力不讨好的事情,效率最高的事情就是通过服务员来知道房间号,通过房间号来找小明——我们也将门牌号称作地址

       C语言中,我们给房间号起了新的名字:指针

       而指针就是这样的作用,标记内存单元的编号,给数据地址,根据数据类型的不同,其内存单元所占的体积不同,比如double类型,为综合的办公室,双精准浮点类型,占据8个字节单位;再比如char类型,可以类比成小巧的办公间,有8个人的办公位置,其大小为1个字节单位。

       我们可以理解成:

       房间号==内存单元的编号==地址==指针

指针的底层原理(硬件方面)

CPU

1

内存

0

0

0

1

1

地址总线

1

0

0

0

1

数据总线

 

控制总线

 
 
 

        我们这一点要从计算机的编址讲起,计算机中的编址——通过硬件设计完成的,像前台服务员要给我们置办房间号一样,它是通过“服务端”来查看“空余房号”给我们安排房间号。

        但是计算机的安排地址不像前台服务员简单给我们安排“空余房号”那样简单,单单通过操作就能完成安排,而是要通过硬件单元相互协同工作。所谓的协同,就是硬件间能够惊醒数据传递。

        硬件与硬件之间是相互独立存在的,我们通过“线”连接起来,。

        CPU与内存之间也有大量数据交互,所以,二者间也是用“线”连接起来

        本文重点讲“指针”,所以只强调地址总线

        32位机器有32根地址总线,由于计算机从晶体管发展以来,通过1,0来表示【电脉冲的有无】,32根地址总线就有2^32含义,而每一种含义都代表一个地址。

        地址信息被下达传给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器

         我们通过前台服务员来获取小明的房间号,这在计算机中,就是我们通过CPU内的MAR获取地址,我们有了房间号,找到小明的速度就加快了,又或者说,我们找到了地址,找到我们想要的数据就变得快捷。

 

#include<stdio.h>
int main() {

    int a = 10;
    int * pa = &a;//取出 a 的地址并存储到指针变量 pa 中

    printf(“%p\n”,&a);//%p指的是打印出16进制的地址

    return 0;
}

       而我们通过服务员(也就是取地址操作符&)来获取小明的地址,我们记得小明的房间号,这是一种存储方式,知道小明房间号的我们,就是一份指针变量。

       地址的存放规律:

0XFFFFFFFF

 

<——高地址

0XFFFFFFFE

 

 
 

 

 
 

 

 

………

  
 

 

 
 

 

 

0X006FFD73

 

int a存放
的地址

0X006FFD72

 

0X006FFD71

 

0X006FFD70

 

 

 

 
 

 

 

………

 

 
 

 

 

0X00000002

8bit

 

0X00000001

1字节

 

0X00000000

1byte

<——低地址

←低地址

指针变量和解引⽤操作符(*)

       从上文我们知道,进行取地址操作(&)后,获得的地址是一个数值,&a获得的是0x006FFD70,这个数值有时候需要存储起来——也就是我们有时候要找小明,那么我们就必须记住他的地址,以备不时之需,而指针变量就是这样的作用。

拆解指针类型

int a = 10;

int * pa = &a;

         我们看到pa的类型是int*

        如果有一个char类型的变量ch,ch的地址,要存放在char*类型的指针变量中。

char ch=’w’;

char* pc=*ch;

解引用操作符

       我们要用到这个信息——小明的房间号,去找小明拿取物品或者存放物品,我们本身需要通过大脑神经突触连接找到关于小明的房间号,而计算机是通过解引用操作符(*)指针来表达pa指向a的地址的数值。

int a = 100;

int* pa = &a;

*pa = 0;

        上段代码*pa的意思就是通过pa中存放的地址,找到指向的空间,*pa实际上就是a本身,我们找到小明的房间号,找到的就是小明本人,我们这时候跟他商讨事情,小明知道了我们的想法(赋值为0),所以*pa=0;

        我们在这里埋下一个伏笔,既然*pa==a;直接写成a=0;就可以了,为什么要大费周章来引入指针?对此,我只能暂时替你回答——我们多了一个路径来修改a的数值,写代码会更加灵活多变——到后面我们再一一揭晓。

指针变量的⼤⼩

       上文提到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。

       指针变量是用来存放地址的,那么指针变量的大小就需要4个字节的空间才行。

       同理,64位的机器中,有64根地址线,一个地址就是64个二进制位组陈大哥二进制序列,存储起来就需要8个字节的空间,指针变量的大小就是8个字节。

printf("%zd\n", sizeof(char *));

printf("%zd\n", sizeof(short *));

printf("%zd\n", sizeof(int *));

printf("%zd\n", sizeof(double *));

在x86的环境下,输出的数值都是4,而在x64环境的环境下,输出的数值都是8.

需要注意到的点是:指针变量的大小和类型是无关的,只要是指针类型的变量,在相同的环境(平台,位数)下,大小都是相同的。

指针变量类型的意义

       指针变量的大小与其类型无关,只要是指针变量,在同一个平台下其字节数都是一样的,为什么还要有各种各样的指针类型?

/代码段1/
#include <stdio.h>

int main() {
    int a = 0x11223344;
    int *pi = &a;
    *pi = 0;
    return 0;
}
/代码段2/
#include <stdio.h>

int main() {
    int a = 0x11223344;
    char *pc = (char *)&a;
    *pc = 0;
    return 0;
}

       通过对这两段代码段的监测我们可以知道,代码1会将a的4个字节全部更改为0,但是代码2只将n的第一个字节改为0.

        于是,我们有这样一种说法:指针的类型决定了,对指针解引用的时候一次能操作几个字符——我们在这里就需要发出疑问,int类型被char*指向是有用的吗?答案是肯定的,char*可以修改int类型中一个字节,这对数据的修正是有利的。

指针+-整数

#include <stdio.h>

int main() {
    int n = 10;
    char *pc = (char*)&n;
    int *pi = &n;

    printf("%p\n", &n);
    printf("%p\n", pc);
    printf("%p\n", pc+1);
    printf("%p\n", pi);
    printf("%p\n", pi+1);

    return 0;
}

        我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可以-1。

结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

void*指针

       在指针类型中有⼀种特殊的类型是 void * 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进⾏指针的+-整数和解引⽤的运算。

使⽤void*类型的指针接收地址:

#include <stdio.h>

int main() {
    int a = 10;
    int* pa = &a;
    int* pc = &a;

    *pa = 10;
    *pc = 0;

    return 0;
}

void* 类型的指针可以接收不同类型的地址,但是⽆法直接进⾏指针运算。

⼀般 void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以 实现泛型编程的效果。使得⼀个函数来处理多种类型的数据

指针运算

指针的基本运算有三种,分别是:

  1. 指针+- 整数
  2. 指针-指针
  3. 指针的关系运算

指针+- 整数

       由于数组在内存中是连续存放的,并且不间断,所以只要知道首元素的地址,就能顺藤摸瓜知道后面所有元素,可以理解成一家子,住在连续的房间号,我们只要知道一家子谁的房间号最小,根据人口数,就可以知道其他人的房间号了。

       int arr[10] = {1,2,3,4,5,6,7,8,9,10};

数组

1

2

3

4

5

6

7

8

9

10

下标

0

1

2

3

4

5

6

7

8

9

地址(*int)

p

p+1

p+2

p+3

p+4

p+5

p+6

p+7

p+8

p+9

#include <stdio.h>

//指针+- 整数

int main(){

        int arr[10] = {1,2,3,4,5,6,7,8,9,10};
        int *p = &arr[0];
        int i = 0;
        int sz = sizeof(arr)/sizeof(arr[0]);

for(i=0; i<sz; i++) {
         printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
         }

         return 0;

}

指针-指针

//指针-指针

#include <stdio.h>

int my_strlen(char *s) {
    char *p = s;
    while (*p != '\0') {
        p++;
    }
    return p - s;
}

int main() {
    printf("%d\n", my_strlen("abc"));
    return 0;
}

指针的关系运算

#include <stdio.h>

int main()

{

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int *p = &arr[0];
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);

    while(p<arr+sz){//指针的⼤⼩⽐较
        printf("%d ", *p);
        p++;
        }

    return 0;

}

指针的使⽤和传址调⽤

strcat的模拟

       要清楚strcat的工作原理:将字符串dest,src传入,src每个下标上的字符赋给与dest相同下标的字符。

       Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination.

——legacy.cplusplus.com

char* my_strcat(char* dest, const char* src){

     char* ret = dest;

     assert(dest != NULL);
     assert(src != NULL);

     while(*dest){
     dest++;
     }

    while((*dest++ = *src++)){
      ;
    }

    return ret;

}

传值调⽤和传址调⽤

#include <stdio.h>

void Swap1(int x, int y) {
    int tmp = x;
    x = y;
    y = tmp;
}

int main() {
    int a = 0;
    int b = 0;

    scanf("%d %d", &a, &b);
    printf("交换前:a=%d b=%d\n", a, b);
    Swap1(a, b);
    printf("交换后:a=%d b=%d\n", a, b);

    return 0;
}
/代码段2:/
#include <stdio.h>

void Swap2(int *x, int *y) {
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

int main() {
    int a = 0;
    int b = 0;

    scanf("%d %d", &a, &b);
    printf("交换前:a=%d b=%d\n", a, b);
    Swap2(&a, &b);
    printf("交换后:a=%d b=%d\n", a, b);

    return 0;
}

       我们发现,在main函数的内部,创建了a和b,a的地址是0x,b的地址是,在调用Swap1函数时,将a和b传递给了Swap1函数,而Swap1函数内部船舰了形参x和形参y接受a和b的数值,但是形参x和形参y分别于a,b的地址不一样,相当于x和y是独立的空间,那么在Swap1函数内部交换x和y的数值,不会影响到a和b。该代码段是典型的传值调用。

       在这里提一下,由于形参暂时存在,每一次函数的调用,都要在栈区创建一份临时空间,而当我们利用完这一份临时空间,这个空间就会被销毁——这是为了保护内存不爆炸而做的。

       而代码段2解决的是1当调用Swap函数的时候,Swap2函数内部操作的就是main函数中的a和b,直接将a和b的数值交换。那么就可以使用指针了,在main函数中将a和b的地址传递给Swap2函数,Swap2函数里边通过地址间接地操作main函数中的a和b,并且达到交换的效果。我们将这种把变量的地址传递给了函数,这种函数调用方式叫:传址调用。

        传址调用能将函数和主调函数(main)之间建立真正的联系,在函数内部可以修改主调函数的变量。所以我们如果要在接下来的工作生产中,通过主调函数的变量值来实现函数的计算,那么就采用传值调用。而如果是函数内部要修改主调函数中的变量的数值,就需要传值调用。

        由于篇幅问题,我们再次另开新章:

指针(2)——数组指针,指针数组http://t.csdnimg.cn/DPhLx

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值