C语言的指针(个人理解)

C语言的指针

指针

1. 内存

1.1 内存的作用

  • 暂存放CPU中的运算数据
  • 暂存与硬盘等外部存储器交换的数据

1.2 物理存储器和存储地址空间

物理存储器:

实际存在的具体存储器芯片

  • 主板上装插的内存条
  • 显示卡上的显示RAM芯片
  • 各种适配卡上的RAM芯片和ROM芯片
存储地址空间:

对存储器编码的范围

  • 编码:对每个物理存储单元(一个字节)分配一个号码
  • 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写

1.3 内存地址

  • 将内存抽象成一个很大的一维字符数组

  • 编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关),这个内存编号我们称之为内存地址

  • 内存中的每一个数据都会分配相应的地址

  • char :占一个字节分配一个地址

  • int : 占四个字节分配四个地址

  • float、struct、函数、数组等

1.4 指针和指针变量

  • 内存区的每一个字节都有一个编号,这就是“地址”

  • 如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号);

  • 指针的实质就是内存“地址”。指针就是地址,地址就是指针

  • 指针是内存单元的编号,指针变量是存放地址的变量;

  • 通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样;

  • 指针变量的大小为4个字节,每个字节都有地址编号;64位操作系统指针变量为8个字节,范围是0x0000000000000000 -0xffffffffffffffff;

2. 指针的定义与使用

  • 指针也是一种数据类型,指针变量也是一种变量

  • 指针变量指向谁,就把谁的地址赋值给指针变量

  • *操作符操作的是指针变量指向的内存空间

2.1 指针的声明

指针变量声明的一般形式为:

type *var-name;

    - type 是指针的基类型,它必须是一个有效的C数据类型;
    - var-name 是指针变量的名称

有效的指针声明:

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch     /* 一个字符型的指针 */

注意:

&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的

2.2 指针的使用

  1. 声明一个实际变量
  2. 声明一个指针变量
  3. 在指针变量中存储实际变量的地址

例如:

int var = 1;
int *ptr;
ptr = &var;

*ptr代表取地址所代表的空间的内容

示例:

// STATEMENT : 指针的创建和使用


#include <stdio.h>

void testPointer() {
    int a = 10;
    int *pInt = &a;
    printf("pInt=%p\n", pInt); //pInt=000000000061FDE4
    printf("pInt=%d\n", *pInt); //pInt=10
    //根据指针修改a的值
    *pInt = 12;
    printf("a = %d", a); //a = 12
}

int main() {
    testPointer();
    return 0;
}

2.3 指针大小

  • 使用sizeof()测量指针的大小,得到的总是:4或8
  • sizeof()测的是指针变量指向存储地址的大小
  • 在32位平台,所有的指针(地址)都是32位(4字节)
  • 在64位平台,所有的指针(地址)都是64位(8字节)
- // STATEMENT : 指针的大小

#include <stdio.h>

int main() {
    int a = 1;
    float b = 2.1f;
    double c = 3.14;
    char e = 'w';
    int *p1 = &a;
    float *p2 = &b;
    double *p3 = &c;
    char *p4 = &e;
    printf("%llu\n", sizeof(p1));//8
    printf("%llu\n", sizeof(p2));//8
    printf("%llu\n", sizeof(p3));//8
    printf("%llu\n", sizeof(p4));//8
}

2.4 指针的宽度(步长)

指针的宽度 = sizeof(将指针变量与指针变量最近的*拖黑,剩下的类型)

宽度也叫做步长;

步长: 指针加1跨过多少个字节;

char    *p        1 
short   *p        2
int     *p        4
Int    **p        sizeof(int  *)   4

示例:

// STATEMENT : 指针的步长


#include <stdio.h>

int main() {
    int num = 0x01020304;
    char *p1 = (char *) &num;//int *
    short *p2 = (short *) &num;
    int *p3 = &num;
    int **p4 = &p3;
    //通过*取指针变量所指向那块空间内容时,取的内存的宽度和指针变量本身的类型有关
    printf("%x\n", *p1);//04
    printf("%x\n", *p2);//0304
    printf("%x\n", *p3);//01020304
    printf("%lld\n", sizeof(*p1)); //1
    printf("%lld\n", sizeof(*p2)); //2
    printf("%lld\n", sizeof(*p3));//4
    printf("%lld\n", sizeof(*p4));//8
    return 0;
}

2.5 野指针和空指针

野指针

定义: 野指针就是没有初始化的指针,指针的指向是随机的

例:

int *p;
*p = 200;
//p 没有初始化,p就是一个野指针
空指针

定义: 将指针的赋值为NULL;

作用: 如果使用完指针,将指针赋值为NULL;这样的话,在使用时判断指针的值是否为NULL,就可以知道指针是否使用过了;

空指针示例:

// STATEMENT : 空指针

#include <stdio.h>

int main() {
    int a;
    //将指针的值赋值为0,0x0000000 =  NULL
    int *p = NULL;//给指针p的内容赋值为0

    *p = 200;//err  因为p保存了0x0000的地址,这个地址是不可以使用的,非法
    printf("%d\n", *p);
    return 0;
}    

2.7 万能指针void *

void *指针可以指向任意变量的内存空间

注意:

  • 不可以定义void类型变量,因为编译器不知道分配多大的空间;
  • 可以定义void *类型,因为指针都是4或者8字节;

万能指针示例:

// STATEMENT : 万能指针 void*

#include <stdio.h>

int main() {
    int n1 = 5;
    double n2 = 3.2;
    void *p1 = &n1;
    void *p2 = &n2;
    //用万能指针获取变量的值
    /*printf("n1=%d\n", *p1);*/
    //不能使用万能指针去获取内存空间中的值,因为void,类型编译器不知道要分配多少空间
    printf("n1=%d\n", *(int *) p1); //n1=5
    printf("n1=%f\n", *(double *) p2); //n1=3.200000
    //先把指针类型由void*转换成其他类型,再获取内存中数值;
    return 0;
}

2.8 const修饰的指针变量

首先,回顾一下const修饰的普通变量,
假设:

const int n = 10;

这说明,我们不能直接通过变量n去修改它的值;但是,却可以通过地址去间接修改变量n的值;

const int *p = &n;

代表不能通过*p去改变p指向空间中的内容

int * const p = &n;

说明p的指向不能被改变;

const int *const p = &n

说明p的指向不能改变而且不能通过*p去直接修改p所指向内存空间的内容;

void testConstPtr() {
    int a = 10;
    //const int *p = &a;
    int *const p = &a;
    *p = 14;
    printf("a=%d", a); //a=14
}

3. 多级指针

二级指针就是指向一个一级指针变量地址的指针

二级指针使用实例:

// STATEMENT : 多级指针

#include <stdio.h>

int main() {
    double a = 3.14;
    //一级指针
    double *p = &a;
    //二级指针
    double **pp = &p;
    //根据二级指针修改a的值
    **pp = 6.28;
    printf("a=%f", a); //a=6.280000
    return 0;
}

4. 指针与数组

4.1 数组名

数组名字是数组的首元素地址,但它是一个常量,不能被修改;

4.2 使用指针操作数组元素

// STATEMENT : 使用指针实现数组的遍历操作


#include <stdio.h>

int main() {
    int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    //让p指向数组首元素地址
    int *p = a;
    int length = sizeof(a) / sizeof(a[0]);
    for (int i = 0; i < length; i++) {
        printf("%d ", *p); //1 2 3 4 5 6 7 8 9
        p++;//每次跨过一个步长的单位
    }
    return 0;
}
/**
 * 通过指针给数组元素赋值
 */
void assignmentArr() {
    int a[8] = {0};
    int *pInt = a;
    for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
        //通过指针给元素赋值
        *pInt = i * 2;
        pInt++;
    }
    //遍历数组
    for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
        printf("%d ", a[i]); //0 2 4 6 8 10 12 14
    }
}
  • 指针+1,跨过一个步长;
  • int *p; step = sizeof(int);

5. 指针加减运算

  • 量指针(类型一致)相减,得到的是中间跨过了多少元素;
  • 两指针相减没有意义(vs里面编译可以通过,但是cLion中编译不通过)
    ;

示例:

void calcOne() {
    int arr[] = {2, 3, 4, 5, 6, 7};
    int *p1 = arr;
    //定义指向末尾元素的指针
    int *p2 = (int *) (&arr + 1) - 1;
    //计算跨过多少元素
    int length = p2 - p1;
    printf("length = %d", length);//length = 5
}
void calcTwo() {
    int arr[] = {2, 3, 4, 5, 6, 7};
    int *p1 = &arr[0];
    int *p2 = &arr[4];
    /*  int num = p2 + p1;*/ //指针不能相加,编译器不通过;
}

补充:

  • []==*(),即[]并不是数组特有的符号
  • p[3]==*(p+3)
int main(){
        //[] == *()
        int  a[10] = { 1,2,3,4,5,6,7,8,9,10 };
        int *p = a;
        for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++){
               //printf("%d ",a[i]);//a[i] == *(a+i)
               //printf("%d ", *(p+i));
               //printf("%d ", p[i]);// p[i]  == *(p+i)
               printf("%d ", *(a + i));
        }
        return 0;
}

6. 指针数组

定义:由指针元素组成的数组;

6.1 指针数组的创建

语法:
type *name[n] = {&a,&b...}
    - type * :指针的类型
    - name   :数组名
    - n      :元素个数
    - {}     :具体的元素

示例1:

// STATEMENT : 指针数组

#include <stdio.h>

void initPointerArray() {
    double a = 1.21;
    double b = 2.24;
    double c = 3.14;
    double *arr[3] = {&a, &b, &c};
    //遍历指针数组,获取对应空间中存放的数据;
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
        printf("%.3f ", *arr[i]); //1.210 2.240 3.140
    }
}
定义指针指向指针数组首地址:
void test2() {
    double a = 1.21;
    double b = 2.24;
    double c = 3.14;
    double *arr[3] = {&a, &b, &c};
    double **k = arr;
    //获取首元素的值
    printf("arr[0] = %.2f\n", **k); //arr[0] = 1.21
    //通过k这个二级指针,遍历该数组;
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
        // printf("%.2f ", *(double *) k[i]); //1.21 2.24 3.14
        printf("%.2f  ", **(k + i));  //1.21  2.24  3.14
    }
}

7. 指针和函数

7.1 函数形参改变实参的值

#include <stdio.h>

/**
 * 交换两个变量的值
 * @param x
 * @param y
 */
void swapValue(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    int x = 1;
    int y = 2;
    swapValue(&x, &y);
    printf("x=%d\n", x); //x=2
    printf("y=%d\n", y); //y=1
    return 0;
}

7.2 数组名做函数参数

把数组名作为参数时出现的问题

代码如下:

#include <stdio.h>

void printArr(int a[6]) {
    for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
        printf("%d ", a[i]);//1 2
    }
}

int main() {
    int arr[6] = {1, 2, 3, 4, 5, 6};
    printArr(arr);
    return 0;
}

原本有6个元素的数组,只打印出了1,2这两个值;

原因:

  1. 当把数组名作为参数传递到printArr中去时,实际上是传递的数组的首地址,而数组作为函数形参会退化为指针,因此sizeof(a)==8;
  2. 这里的sizeof(a[0])等价于sizeof(*a[0]) -> sizeof(*(a+0)) -> sizeof(*a) -> sizeof(1) == 4;
  3. 因此,sizeof(a) / sizeof(a[0]) == 2;所以条件为i<2;因此只打印出了两项内容;
注意:
  • 数组作为函数形参会退化为指针
  • 如果想把数组作为参数传递,则需要传递一个指针类型的形参和一个int的形参(代表数组长度);
解决方法

定义一个指针类型的变量当做形参,接收数组的首地址;但是这样的话无法知道传递过去的数组的长度,因此还需要一个int类型的参数,专门用来接收数组的长度;

代码示例:

#include <stdio.h>

void printValue(int *array, int length) {
    for (int i = 0; i < length; i++) {
        printf("%d ", *(array + i)); //1 2 3 4 5 6
    }
}

int main() {
    int arr[6] = {1, 2, 3, 4, 5, 6};
    printValue(arr, sizeof(arr) / sizeof(arr[0]));
    return 0;
}

7.3 指针做为函数的返回值

// STATEMENT : 指针作为返回值

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

int num = 0;//在函数外面定义的变量叫全局变量,整个工程都可以使用
//整个变量程序启动开辟空间,直到程序结束释放空间
int *getNum() {
    //{}中定义的变量叫局部变量,局部变量在函数结束之后的空间会被释放
    srand(time(NULL));
    num = rand();
    return &num;
}

int main() {
    int *p = getNum();
    printf("%d\n", *p); //10048
    return 0;
}

8. 指针和字符串

8.1 字符指针

// STATEMENT : 字符数组和指针

#include <stdio.h>
#include <string.h>

/**
 * 通过指针遍历字符数组
 */
void charPointer() {
    char ch[] = "whatrudoing";
    char *c = ch;
    for (int i = 0; i < sizeof(ch) / sizeof(ch[0]); i++) {
        printf("%c ", *(c + i));
    }
    printf("\n");
    printf("%s\n", c); //%s打印字符串,要的是首元素的地址
    printf("%s\n", c + 2); //atrudoing; 表示获取从首元素移动两个元素之后的字符串
    printf("%c\n", *(c + 3));//t ; 获取第四个字符

    printf("%llu\n", sizeof(ch));//12(字符串长度+!)
    printf("%llu\n", sizeof(c));//8(64位地址的长度)
    // sizeof(ch) != sizeof(c);

    printf("%llu\n", strlen(ch));//11
    printf("%llu\n", strlen(c));//11
    //strlen(ch) == strlen(c);
}

8.2 字符指针做函数参数

// STATEMENT : 字符指针作为函数参数

#include <stdio.h>
#include <string.h>

/**
 * @method:合并两个字符串
 * @param src :目标字符串
 * @param dest:要合并的字符串
 */
void concatString(char *src, char *dest) {
    int len = strlen(src);
    int i = 0;
    for (i = 0; i < strlen(dest); i++) {
        *(src + i + len) = *(dest + i);
    }
    //加入字符串结束符号
    *(src + i + len + 1) = 0;
}

int main() {
    char a[100] = "hello";
    char b[100] = "123456";
    concatString(a, b);
    printf("%s", a); //hello123456
    return 0;
}

8.3 const修饰的指针变量

int main(void) {
    //const修饰一个变量为只读
    const int a = 10;
    //a = 100; //err

    //指针变量, 指针指向的内存, 2个不同概念
    char buf[] = "aklgjdlsgjlkds";

    //从左往右看,跳过类型,看修饰哪个字符
    //如果是*, 说明指针指向的内存不能改变
    //如果是指针变量,说明指针的指向不能改变,指针的值不能修改
    const char *p = buf;
    // 等价于上面 char const *p1 = buf;
    //p[1] = '2'; //err
    p = "agdlsjaglkdsajgl"; //ok

    char *const p2 = buf;
    p2[1] = '3';
    //p2 = "salkjgldsjaglk"; //err

    //p3为只读,指向不能变,指向的内存也不能变
    const char *const p3 = buf;

    return 0;
}

8.4 字符指针数组

  • 表示为char *ch[];
  • 可以用来保存字符串的首地址
  • 通过遍历每一个元素,在并在元素前加*,可得到每一个字符串;
// STATEMENT : 字符指针数组

#include <stdio.h>

//字符指针数组
//是一个数组,每一个元素是字符指针
int main() {
    char *num[3] = {"heihei", "haha", "xixi"};
    //定义一个指针保存num数组首元素的地址   &num[0]  num
    char **p = num;
    for (int i = 0; i < 3; i++) {
        //      printf("%s\n",*(p+i));
        printf("%s\n", p[i]);
    }
    printf("%c\n", *(*(p + 1) + 3));// *(p[1]+3)   == p[1][3],a
    //for (int i = 0; i < 3; i++)
    //{
    //      printf("%s\n",num[i]);
    //}
    printf("%c\n", *num[0]);//h
    printf("%c\n", *(num[1] + 1));//a
    printf("%c\n", *(num[2] + 2));//x
    return 0;
}

8.5 指针数组做为main函数的形参

int main(int argc,char *argv[])
    - argc表示参数的个数
    - *argv这个数组接收具体的参数
说明:
  • main函数是操作系统调用的,第一个参数标明argc数组的成员数量,argv数组的每个成员都是char *类型
  • argv是命令行参数的字符串数组
  • argc代表命令行参数的数量,程序名字本身算一个参数
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值