C语言

指针

关于内存那点事

存储器:存储数据器件

  • 外存
    • 外存又叫外部存储器,长期存放数据,断电不丢失数据
    • 常见的有外部存储器:硬盘、flash、rom、U盘、光盘、磁带
  • 内存
    • 内存又叫内部存储器,暂时存放数据,断电数据丢失
    • 常见的内存设备:ram、DDR
  • 虚拟内存:操作系统虚拟出来的内存
  • 32bit 32根寻址总线

在写应用程序的时候,咱们看到的都是虚拟地址。

在运行程序的时候,操作系统会将 虚拟内存进行分区

  1. 主要存放局部变量(在函数内部,或复合语句内部定义的变量)

  2. 静态全局区

    1. 未初始化的静态全局区

      静态变量(定义的时候,前面加static修饰)。或全局变量,没有初始化的,存放在此区

    2. 初始化的静态全局变量

      全局变量、静态变量、赋过初值的、存放在此区

  3. 代码区

    存放咱们的程序代码

  4. 文字常量区

    存放常量的

内存以字节为单位来存储数据的,咱们可以将程序中的虚拟寻址空间,看成一个很大的维的字符数组

指针的相关概念

  • 系统给虚拟内存的每个存储单元分配了一个编号,从
  • 0x00 00 00 00 ~ 0xff ff ff ff
  • 这个编号咱们称之为**地址**.
  • 指针就是地址
  • 指针变量:是个变量,是个指针变量,即这个变量用来存放一个地址编号
  • 在32位平台下,地址总线是32位的,所以地址是32位编号,所以指针的变量是32位的即4个字节。

注意:

  • 无论什么类型的地址,都是存储单元的编号,在**32位平台下**都是4个字节。
  • **即任何类型的指针变量都是4个字节大小**。
  • **对应类型的指针变量,只能存放对应类型的变量地址**。
  • 举例:整型的指针变量,只能存放整型变量地址

扩展:

  • 字符变量 char ch = 'b';ch占一个字节,它有一个地址编号,这个地址编号就是ch的地址。

  • 整型变量 int a = 0x12 34 56 78; a 占4个字节,它占有4个字节的存储单元,有四个地址编号。

  • 整型变量a 占四个字节,分别对应四个地址,多字节的情况下地址是变量最小的,也就是 0x000020000

  • 地址 存放的东西
    0x00002003 0x12
    0x00002002 0x34
    0x00002001 0x56
    0x00002000 0x78
    0x00001fff ‘b’

指针的定义方法

  1. 简单的指针变量

    数据类型 * 指针变量名;

    int * p; 定义了一个指针变量p

    在定义指针变量的时候 * 是用来修饰变量的,说明变量p是个指针变量

    变量名是 p

  2. 关于指针的运算符

    & 取地址 、 * 取值

    int a = 0x1234abcd;
    int *p; // 在定义指针的时候*表示修饰的意思,修饰p是个指针变量
    p = &a; // 把 a 的地址给p赋值 &是取地址符

    p 保存了 a 的地址,也可以说 p 指向了 a

    p 和 a 的关系分析: a 的值是0x1234abcd, 假如 a 的地址是: 0xbf e8 98 68

分析:

  1. 在调用的时候 * 代表取值的意思, *p 就相当于p指向的变量,即a,
  2. 故 num=*p 和 num=a 的效果是一样的。
  3. 所以说num的值为 0x1234abcd

扩展:

  • 如果在一行中定义多个指针变量,每一个指针变量前面都需要加*来修饰
  • int *p,*q// 定义了两个整数的指针变量p 和 q
  • int *p,q;// 定义了一个整型指针变量p,和整型变量q
int main()
{
int a = 100,b = 200;
int *p_1, *p_2 = &b; // 表示该变量的类型是一个指针变量,指针的变量名是 p_1 而不是 *p_1
// p_1 在定义的时候没有赋初值, *p_2 赋了初值
p_1 = &a; // p_1先定义 后赋值
printf("%d\n",a);
printf("%d\n",*p_1);
printf("%d\n",b);
printf("%d\n",*p_2);
return 0;
}
  • 指针的大小:在32位系统下,所有类型的指针都是4个字节
  • 指针变量是用来存放地址编号的,不管你是一个什么类型的,他都是存放的地址
int main()
{
int a = 0x1234abcd;
int* p;
p = &a;
// 打印地址编号 使用 %p
printf("&a = %p\n", &a);
printf("p = %p\n",p);
return 0;
}

指针的分类

  • 注意:多字节变量,占多个存储单元,每个存储单元都有编号
  • C语言规定,存储单元编号最少的那个编号,是多字节变量的地址编号。
  1. 字符指针

    字符型数据地址

    char *p; //定义了一个字符指针变量,只能存放字符型数据的地址编号
    char ch;
    p=&ch;
  2. 短整型指针

    short int *p; //定义了一个短整型的指针变量p,只能存放短整型变量的地址
    short int a;
    p = &a;
  3. 整型指针

    int *p; //定义了一个整型的指针变量p,只能存放整型变量的地址
    int a;
    p = &a;
  4. 长整型指针

    long int *p;  //定义了一个长整型的指针变量p,只能存放长整型变量的地址
    long int a;
    p = &a;
  5. float 型指针

    float *p; // 定义了一个float 型的指针变量p,只能存放float型变量地址
    float a;
    p = &a;
  6. double 型指针

    double *p;
    double a;
    p = &a;
  7. 函数指针

  8. 结构体指针

  9. 指针的指针

  10. 数组指针

  11. 通用指针 void*p;

  • 结论:**无论什么类型的指针变量,在32位系统下,都是4个字节**。
  • **指针只能存放对应类型的变量的地址编号**。

指针和变量的关系

int a = 100;
int *p;
p = &a;

在程序中,引入变量的方法:

  1. 通过变量的名称

    int a;
    a = 100;
  2. 通过指针变量来引入变量

    int *p; // 在定义的时候, *不是取值的意思,而是修饰的意思,修饰p是个指针变量
    p = &a; // 取a的地址给p赋值,p保存了a的地址,也可以说p指向了a
    *p=100;// 在调用的时候*是取值的意思,*指针变量 等价于指针指向的变量

    注:指针变量在定义的时候可以初始化

    int a;
    int *p = &a; //用a的地址,给p赋值,因为p是指针变量

    **指针就是用来存放变量地址**的.

    #include <stdio.h>

    int main()
    {
    int *p1,*p2,temp,a,b; // 两个指针,三个变量
    p1 = &a;
    p2 = &b;
    printf("请输入:a,b的值: \n");
    scanf_s("%d %d",p1,p2); // 给 p1 和 p2 指向的变量赋值
    temp = *p1; // 用p1 指向的变量(a)给temp赋值
    *p1 = *p2; // 用p2指向的变量(b)给p1指向的变量(a) 赋值
    *p2 = temp; // temp给p2指向的变量(b)赋值
    printf("a = %d b = %d \n", a,b);
    printf("*p1 = %d *p2 = %d", *p1,*p2);
    return 0;
    }
    • & + 变量名 == 指针
    • *+指针名=变量.

扩展:

  • 对应类型的指针,只能保存对应类型数据的地址,

  • 如果想让不同类型的指针相互赋值的时候,需要强制类型转换

  • void *p

#include <stdio.h>

int main()
{
int a = 0x12345678,b = 0xabcdef66; //int 是四个字节
char *p1,*p2; //char 是一个字节
printf("%0x %0x\n",a,b);
p1 = (char *)&a;
p2 = (char *)&b;
printf("%0x %0x \n",*p1,*p2);
p1++; // 因为强制转换成char了,字节的指向本来是最后一个,++后就是倒数第二个
p2++;
printf("%0x %0x\n",*p1,0xff & (*p2));
return 0;
}
/*
12345678 abcdef66
78 66
56 ef
*/

  1. ***+指针 取值,取几个字节,用指针类型决定的,指针为字符型指针取一个字节,指针为整型指针取4个字节,指针为double型指针取8个字节**

  2. 指针++ 指向下个对应类型的数据

    字符指针++,指向下个字符数据,指针存放的地址编号加1

    整型指针++,指向下个整型数据,指针存放的地址编号加4

指针和数组元素之间的关系

  • 变量存放在内存中,有地址编号,咱们定义的数组,是多个相同类型的变量集合

  • 每个变量都占内存空间,都有地址编号

  • 指针变量当然可以存放数组元素地址。

    int a[5];
    int *p = &a[0];
    // 指针变量p 保存了数组中第0个元素的地址, 即a[0]的地址

数组元素的**引入方法**.

  1. 数组名[下标]

    int a[5];

    a[2] = 100;

  2. 指针名加下标

    int a[5];
    int *p;
    p = a;
    p[2]=100; // 相等于 a[2] = 100;

    补充:C语言规定:数组的名字就是数组的首地址,即第0个元素的地址,就是a[0],是个常量

    注意:p和a的不同,p是指针变量,而a是个常量。所以可以用等号给p赋值,但不能给a赋值。

  3. 通过指针变量运算加取值的方法来引用数组元素

    int a[5];
    int *p;
    p=a;
    *(p+2)=100; //相等于a[2] = 100
    // 解释: p 是第0个元素的地址,p+2是a[2]这个元素的地址.
    // 对第二个元素的地址取值,即a[2]
  4. 通过数组名+取值的方法引用数组的元素

    int a[5];
    *(a+2)=100; // 相等于a[2] = 100;
    // 注意: a+2 是 a[2]的地址.这个地方并没有给a赋值.

    例如:

    #include <stdio.h>

    int main()
    {
    int a[5] = {0,1,2,3,4};
    int *p;
    p = a;
    printf("a[2] = %d\n",a[2]);
    printf("p[2] = %d\n",p[2]);
    printf("*(p+2) = %d\n",*(p+2));
    printf("*(a+2) = %d\n",*(a+2));
    printf("p = %p\n",p);
    printf("p+2 = %p\n",p+2);
    return 0;
    }

指针的运算

  1. 指针可以加上一个整数,往下指几个它指向的变量,结果还是个地址

    前提:指针指向的数 组元素的时候,加一个整数才有意义。

    int a[5];
    int *p;
    p = a;
    p+2; // p是a[0]的地址,p+2 是&a[2]
    // 假如p保存的地址是 2000的话, p+2 代表的地址编号是 2008
    char buf[5];
    char *q;
    q=buf;
    q+2; // 相等于&buf[2]
    // 假如: q中存放的地址编号是 2000 的话,q+2 代表的地址编号是 2002
  2. 两个相同类型的指针可以比较大小.

    前提:只有两个**相同类型的指针指向同一个数组元素**的时候,比较大小才会有意义

    **指向前面元素的指针 小于 指向后面元素的指针**。

    int a[10];
    int *p,*q;
    p = &a[1];
    q = &a[6];
    if(q > p){
    printf("q > p");
    }
  3. 两个相同类型的指针可以做减法

    前提:只有两个**相同类型的指针指向同一个数组元素**的时候,做减法会有意义

    做减法的结果是,两个指针指向的中间有多少个元素

    #include <stdio.h>
    int main()
    {
    int a[10];
    int *p,*q;
    p = &a[1];
    q = &a[6];
    printf("%d\n",q-p); // 中间相差了几个元素
    return 0;
    }
  4. 两个相同类型的指针可以相互赋值

    注意:只有相同类型的指针才可以相互赋值(void*类型的除外)

    int *p,*q;
    int a;
    p = &a; // 保存了a的地址
    q = p; //用p给q赋值,q也保存了a的地址,指向a

    注意:C语言规定数组的名字,就是数字的首地址,就是数组的第0个元素的地址

    int *p;
    int a[10];
    p = a; p = &a[0]; 这两种方法是等价的

指针数组

目的就是把多个变量的指针存到一起,从而出现指针数组

  1. 指针和数组关系.

    1. 指针可以保存数组元素的地址
    2. 可以定义一个数组,数组中有**诺干个相同类型变量**,这个数组被称为指针数组

    **指针数组的概念**:

    指针数组本身是个数组,是个指针数组,是**诺干个想用类型的指针变量构成的集合**。

  2. 指针数组的定义方法

    类型说明符 * 数组名[元素个数];

    int *p[5];//定义了一个整型的指针元素数组p,有5个元素p[0]~p[5],每个元素都是int* 类型的变量
    int a;
    p[0]=&a;
    int b[10];
    p[1] = &b[5];
    // p[2] *(p+2)是等价的,都是指针数组中的第2个元素

    打印字符数组

    #include <stdio.h>
    int main()
    {
    char *name[5] = {"Hello","China","everyday","pink","green"};
    int i;
    for(i = 0; i < 5; i++){
    printf("%s\n",name[i]);
    }
    return 0;
    }

指针的指针

指针的指针,其实就是指针的地址

  • 咱们定义一个指针变量本身指针变量占4个字节,指针变量也有编号。
  • 例:
  • int a = 0x12345678;
  • 假如:a的地址是 0x00002000
  • int *p;
  • p = &a;
  • 则p中存放的是a的地址编号即 0x00002000
  • 因为**p也占四个字节,也有自己的地址编号,即指针变量的地址,即指针的指针**(就是套娃
  • 我们定义一个变量存放p的地址编号,这个变量就是指针的指针
  • int **q;
  • q=&p; // 保存了p的地址,也可以说q指向了p
  • 则q里存放的就是0x00003000
#include<stdio.h>
int main()
{
int a = 0x12345678;
int *p,**q,***m; // 一级指针p,二级指针q,三级指针m
p = &a;
printf("&a=%p\n",&a); // 一级
printf("p =%p \n",p);
q = &p;
printf("&p =%p\n",&p); // 二级
printf("q = %p\n",q);
m = &q;
printf("&q =%p\n",&q);
printf("m = %p\n",m);
// 打印指着最终指向的内容
printf("*p=%x\n",*p);
printf("**q=%x\n",**q);
printf("***m=%x\n",***m);
return 0;
}

字符串和指针

字符串的概念:

  • 字符串就是以**‘\0’结尾的诺干个字符的集合**,比如:”helloworld”。
  • 字符串的地址,是**第一个字符的地址。如:字符串”hello” 的地址,其实就是字符串’h’的地址**。
  • 我们可以定义一个字符指针变量保存字符串的地址,比如:char *s="hello";

字符串的存储形式:数组、文字常量区、堆.

  1. 字符串其实存放在数组中

    其实就是在内存(栈、静态全局区)中开辟了一段空间来存放字符串。

    char string[100]="I love C!";

    定义了一个字符串string,用来存储多个字符,并且用”I love C!”给string数组初始化,字符串”I love C!” 存放在string 中。

  2. 字符串存放在文字常量区

    在文字常量区开辟了一段空间存放字符串,将字符串的**首地址**赋给指针变量。

    char *str="I love C!";

    定义了一个指针变量str,只能存放字符地址编号

    I love C! 这个字符串中的字符不是存放在str指针变量中

    str只是存放了字符 I 的地址编号,”I love C!” 存放在文字常量区

  3. 字符串可以存放在堆区

    使用malloc等函数在堆区申请空间,将字符串拷贝到堆区

    char *str=(char*)malloc(10*sizeof(char));//动态申请了10个字节的存储空间,首地址给str赋值。

注意:

  • 普通全局数组,内存分配在静态全局区
  • 普通局部数组,内存分配在栈区。
  • 静态数组(静态全局数组、静态局部数组),内存分配在静态全局区

字符串的可修改性:

字符串内容是否可以修改,取决于字符串存放在那里

  1. 存放在数组中的字符串的内容是可以修改的

    chat str[100]="I love C!";

    str[0]='y';// 正确可以修改的

    注: 数组没有同const 修饰

    #include<stdio.h>

    int main()
    {
    char str[50] = "I love C!";
    printf("str=%s\n",str);
    str[0]='y';
    printf("str=%s\n",str);
    return 0;
    }
  2. **文字常量区里的内容是不可修改**的

    chat *str="I love C!";

    *str='y';// 错误,I存放在文字常量区,不可修改

    注:

    1. str指向文字常量区的时候,它指向的**内容不可被修改**.
    2. str是指针变量可以指向别的地方,即**可以给str重新赋值**,让它指向别的地方
  3. 堆区的内容是可以修改的

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    int main()
    {
    char *str;
    str = (char*)malloc(20);
    strcpy(str,"I love C!");
    printf("str=%s\n",str);
    *str = 'y'; // 可以被修改,因为堆区内容是可修改的
    printf("str=%s\n",str);
    return 0;
    }

    str**指向的堆区的时候,str指向的内存内容是可以被修改**的

    **str是指针变量,也可以指向别的地方**。即可以给str重新赋值,让它指向别的地方

注意:str指针指向的内存能不能被修改,要看str指向哪里。

  • str指向文字常量区的时候,内存里的内容不可修改
  • str指向数组(非const修饰)、堆区的时候,他指向内存的内容是可以修改的

初始化

  1. 字符数组初始化

    char buf_aver[20]="hello world";

  2. 指针指向文字常量区,初始化

    char *buf_point="hello world";

  3. 指针指向堆区,堆区存放字符串

    不能初始化,只能先给指针赋值,让指针指向堆区,再使用strcpy、scanf 等方法吧字符串拷贝到堆区。

    char *buf_heap;
    buf_heap=(char*)malloc(15);
    strcpy(buf_heap,"hello world"); // 两种方式,一般使用第一种,第二种可能会警告
    scanf("%s",buf_heap);

使用时赋值

  1. 字符数组:使用scanf 或者 strcpy

    char buf[20]="hello world";
    buf="hello kitty"; // 错误,因为字符数组的名字是个常量,不能用等号给常量赋值
    strcpy(buf,"hello kitty"); // 正确,数组中的内容可以修改的
    scanf("%s",buf); // 正确,数组中的内容是可以修改的
  2. 指针指向文字常量区

    char *buf = "hello world";
    buf="hello kitty"; // 正确,buf指向另一个字符串
    stcpy(buf,"Hello kitty"); // 错误,这种情况,buf指向的是文字常量区,内容只读。
    // 当指针指向的文字常量区的时候,不能通过指针修改文字常量区的内容
  3. 指针指向堆区,堆区存放字符串

    char *buf_heap;
    buf_heap=(char*)malloc(15);
    strcpy(buf_heap,"hello world"); // 两种方式,都可以
    scanf("%s",buf_heap);

字符串和指针的总结

  1. 指针可以指向文字常量区

    1. 指针指向的文字常量区的内容不可改变
    2. 指针的指向可以改变,即可以给指针变量重新赋值,指针变量指向别的地方。
  2. 指针可以指向堆

    1. 指针指向的堆区的内容可以修改。
    2. 指针的指向可以改变,即可以给指针变量重新赋值,指针变量指向被动地方
  3. 指针也可以指向数组(非const修饰)

    char buf[20]="hello world";
    char *str=buf;

    这种情况下

    1. 可以修改buf 数组的内容
    2. 可以通过 str 修改 str 指向的内存的内容,即数组buf的内容
    3. 不能通过buf赋值 buf="hello Kitty"; 错误的
    4. 可以给str赋值,及str指向别处. str=”Hello Kitty”;