C 语言笔记:指针(上)

一、内存相关知识

存储器:存储数据的器件

(一)外存(外部存储器)

长期存放数据,掉电后不丢失数据。
常见的外存设备:硬盘、flash、rom、U 盘、磁带。

(二)内存(内部存储器)

暂时存放数据,掉电后数据丢失。
常见的内存设备:ram、DDR

1. 内存分类

物理内存:看得见摸得着的存储设备
虚拟内存:操作系统虚拟出来的内存。当一个进程被创建时,或程序运行时,都会分配虚拟内存。虚拟内存和物理内存之间存在映射关系。

在 32 位操作系统中,每个进程(运行着的程序)的寻址范围是 4G,即
0x00 00 00 00 ~ 0xff ff ff ff
写程序时,看到的都是虚拟内存。

2. 虚拟内存的分区

程序运行时,操作系统会将虚拟内存进行分区。
(1)堆
在动态申请内存的时候,在堆里开辟内存。

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

(3)静态全局区
a. 未初始化的静态全局区
静态变量(定义的时候,前面加static修饰),或全局变量 ,没有初始化的,存在
此区
b. 初始化的静态全局区
全局变量、静态变量,赋过初值的,存放在此区

(4)代码区
存放程序代码

(5)文字常量区
存放常量的。

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

二、指针的相关概念

我们把内存比作一栋大楼。存储单元就是大楼里的一个个房间。
操作系统给每个“房间”分配了一个“门牌号”。这个“门牌号”就是指针。指针从 0x00 00 00 00 ~ 0xff ff ff ff 。

在大多数现代计算机中,每个存储单元可以存储一个字节的数据。也就是说,一个“房间”有一个字节那么大,能存放一个字节的数据。

指针变量:用来存放一个“门牌号”。它可以通过“门牌号”来找到“房间”里存放的内容。如果用普通变量保存“门牌号”,只会保存一个常量。

在 32 位平台下,地址总线是 32 位的,所以地址是 32 位编号,所以指针变量是 32 位的,即 4 个字节。

注意:

  1. 无论什么类型的地址,都是存储单元的编号,在 32 位平台下都是 4 个字节,即任何类型的指针变量都是 4 个字节大小。无论“房间”里放了什么东西,“门牌号”还是那个“门牌号”,那么存放“门牌号”的变量也就都是那么大。
  2. 对应类型的指针变量,只能存放对应类型的变量的地址。
    举例:整型的指针变量,只能存放整型变量的地址。

扩展:

  1. 字符变量 char ch; 占 1 个字节,也就是说,一个“房间”就放得下它,那么它就只有 1 个“门牌号”,这个“门牌号”就是 ch 的指针。
  2. 整型变量 int a; 占 4 个字节,也就是说,4 个“房间”才放得下它,那么它就有 4 个“门牌号”。
    那为了找到存放 a 的“房间”,应该去找哪个“门牌号”呢?
    最小的编号。

三、指针的定义方法

(一)简单的指针

数据类型 * 指针变量名
int * p; // 定义一个指针变量 p

* 用来修饰变量,说明 p 是个指针变量

(二)关于指针的运算符

& 取地址符,取指针变量的地址* 取指针变量对应的

#include <stdio.h>

int main()
{
    // 定义一个普通变量
    int a = 100;
    // 定义一个指针变量
    int *p;

    // 给指针变量赋值
    p = &a; // 将a的地址保存在p中

    printf("a = %d %d\n", a, *p);
    printf("&a = %p %p\n", &a, p);
    return 0;
}

执行结果:

a = 100 100
&a = 00000062635ffd04 00000062635ffd04

举个例子(这里的地址编号为虚构):地址 0x1001, 0x1002, 0x1003, 0x1004 里面存放的是整型数据a = 100;地址 0x1234 里面存放的是 a 的首地址 0x1001 .

a <==> *p

p <==> &a

扩展:如果在一行中定义多个指针变量,每个指针变量前面都需要加 * 来修饰。
int *p,*q; 定义了两个整型的指针变量pq
int*p,q; 定义了一个整型指针变量p,和整型的变量q

(三)指针大小

在 32 位系统下,所有类型的指针都是 4 个字节
在 64 位系统下,所有类型的指针都是 8 个字节
我下载的是 64 位的

#include <stdio.h>

int main()
{
    char *a;
    short *b;
    int *c;
    long *d;
    float *e;
    double *f;

    printf("sizeof(a) = %d\n", sizeof(a));
    printf("sizeof(b) = %d\n", sizeof(b));
    printf("sizeof(c) = %d\n", sizeof(c));
    printf("sizeof(d) = %d\n", sizeof(d));
    printf("sizeof(e) = %d\n", sizeof(e));
    printf("sizeof(f) = %d\n", sizeof(f));
    return 0;
}

执行结果:

sizeof(a) = 8
sizeof(b) = 8
sizeof(c) = 8
sizeof(d) = 8
sizeof(e) = 8
sizeof(f) = 8

不管“房间”里存了什么,“门牌号”它都是那么长。
不管地址内的空间多大,地址编号的长度是一样的。

四、指针的分类

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

注: 多字节变量, 占多个存储单元, 每个存储单元都有地址编号。C 语言规定, 存储单元编号最小的那个编号, 是多字节变量的地址编号。
4. 长整型指针

long int *p;  //定义了一个长整型的指针变量 p, 只能存放长整型变量的地址
long int a;
p =&a;
  1. float 型的指针
float *p;//定义了一个 float 型的指针变量 p, 只能存放 float 型变量的地址
float a;
p =&a;
  1. double 型的指针
double *p;//定义了一个 double 型的指针变量 p, 只能存放 double 型变量的地址
double a;
p =&a;
  1. 函数指针
  2. 结构体指针
  3. 指针的指针
  4. 数组指针

五、指针和变量的关系

指针可以存放变量的地址编号.

在程序中, 引用变量的方法

(一)直接通过变量的名称

int a;
a=100;

(二)可以通过指针变量来引用变量

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()
{
    // 定义指针变量p1, p2和普通变量a, b, temp
    int *p1, *p2, a, b, temp;
    p1 = &a;//把a的地址赋给p1
    p2 = &b;//把b的地址赋给p2

    *p1 = 12;
    *p2 = 3;

    temp = *p1;//把 a 的值赋给 temp
    *p1 = *p2;//把 b 的值赋给 a
    *p2 = temp;//把 a 的值赋给 b

    printf("a = %d b = %d\n", a, b);
    printf("*p1 = %d *p2 = %d\n", *p1, *p2);
    return 0;
}

执行结果:

a = 3 b = 12
*p1 = 3 *p2 = 12

扩展:
对应类型的指针, 只能保存对应类型数据的地址,如果想让不同类型的指针相互赋值的时候,需要强制类型转换。
当对一个指针进行递增(p++)操作时,指针会移动到下一个相同类型的元素。

例子:

#include <stdio.h>

int main()
{
    int a = 0x1234, b = 0x5678;//定义 2 个整型变量。1 个 int 数据占 4 个字节
    char *p1, *p2;//定义 2 个字符型指针变量,一个 char 型指针变量只能存储 1 个字节
    printf("a = %#x, b = %#x\n", a, b);
    printf("&a = %#x, &b = %#x\n", &a, &b);
    p1 = (char *)&a;//将 int 类型的地址转换为 char 类型的地址
    p2 = (char *)&b;
    printf("*p1 = %#x, *p2 = %#x\n", *p1, *p2);
    p1++;
    p2++;
    printf("*p1 = %#x, *p2 = %#x\n", *p1, *p2);
    return 0;
}

执行结果:

a = 0x1234, b = 0x5678
&a = 0x1a3ffddc, &b = 0x1a3ffdd8
*p1 = 0x34, *p2 = 0x78
*p1 = 0x12, *p2 = 0x56

首先,1 个 int 类型数据占 4 个字节。由执行结果 &a = 0x1a3ffddc, &b = 0x1a3ffdd8 可推知,a 的值 0x1234 在内存中的存储方式如下(假设地址从左到右增加):

类似地,b 的值 0x5678 在内存中的存储方式如下:

p1 = (char *)&a;p2 = (char *)&b; 之后, p1 存放的是地址 0x1a3ffddcp2 存放的是地址 0x1a3ffdd8
然后,执行 *p1*p2 时,实际上是在读取 ab 的最低有效字节,即 0x340x78
执行 p1++p2++,意味着 p1p2 现在指向它们原来指向地址的下一个字节,即 0x120x56

注意:

  1. *p 取值,取几个字节,由指针类型决定。
    指针为字符型,则取 1 个字节;
    指针为整型,则取 4 个字节;
    指针为 double 型,则取 8 个字节。
  2. p++ 指向下个对应类型的数据
    字符 p++ ,指向下个字符数据,指针存放的地址编号加 1
    整型 p++ ,指向下个整型数据,指针存放的地址编号加 4

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

(一)指针变量可以存放数组元素吗?

变量存放在内存中,有地址编号。数组是多个相同类型的变量的集合。
每个变量都占内存空间,都有地址编号。指针变量当然可以存放数组元素的地址。

int a[10];//定义一个整型数组
int *p;//定义一个整型指针
p=&a[0];//把数组 a 第 0 个数组的地址赋给 p

上面三行代码等价于 int *p = &a[0]; ,给整型指针变量初始化。

(二)数组元素的引用方法

方法一:数组名[下标]
int a[10];
a[2] = 100;
方法二:指针名[下标]
int a[10];
int *p;
p = a;
p[2] = 100;

上例中 pa 等价。

**补充:**在 C 语言中,数组名是一个特殊的概念。它通常被用来表示数组的第一个元素的地址。换句话说,当你使用数组名时,你实际上是在使用一个指向数组第一个元素的指针。
注意:pa 的不同, p 是指针变量, 而 a 是个常量。 所以可以用等号给 p 赋值, 但不能给 a 赋值。

方法三:对指针取值
int a[10];
int *p;
p = a;
*(p + 2) = 100;

*(p + 2) 相当于 a[2]p 是第 0 个元素的地址,p + 2 是第 2 个元素的地址。对第二个元素的地址取值,即 a[2]

例子:

#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);
    printf("&a[0] = %p\n", &a[0]);
    printf("&a[2] = %p\n", &a[2]);
    return 0;
}

执行结果:

a[2] = 2
p[2] = 2
*(p + 2) = 2
*(a + 2) = 2
p     = 000000acb59ff800
p + 2 = 000000acb59ff808
&a[0] = 000000acb59ff800
&a[2] = 000000acb59ff808

在上例中,*(a + n)*(p + 2)a[n]p[n]

七、指针的运算

(一)指针加整数

#include <stdio.h>

int main()
{
    int a[10];
    int *p;
    p = a;

    printf("&p[0] = %p\n", &p[0]);
    printf("&p[2] = %p\n", &p[2]);
    return 0;
}

p + 2 就是 a[2] 的地址
执行结果:

&p[0] = 00000089ecbffb60
&p[2] = 00000089ecbffb68

&p[0]&p[2] 差 8 个字节,加一个整数移动的字节与指针变量的类型有关系。
由于 p 是一个整型指针,p + 2 实际上是将指针向前移动了 2 * sizeof(int) 个字节。在大多数现代计算机系统中,一个整型(int)通常占用 4 个字节。

(二)两个相同类型的指针可以比较大小

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

#include <stdio.h>

int main()
{
    int a[10];
    int *p, *q;
    p = &a[1];
    q = &a[6];

    if(p > q)
    {
        printf("p > q\n");
    }
    else if(p == q)
    {
        printf("p = q\n");
    }
    else
    {
        printf("p < q\n");
    }

    return 0;
}

执行结果:

p < q

(三)两个相同类型的指针可以做减法

前提:必须是两个相同类型的指针指向同一个数组的元素的时候,做减法才有意义。
做减法的结果是,两个指针指向的中间有多少个元素。

#include <stdio.h>

int main()
{
    int a[10];
    int *p, *q;
    p = &a[1];
    q = &a[6];
    printf("q - p = %d\n", q - p);

    return 0;
}

执行结果:

q - p = 5

这表示 q 和 p 之间有 5 个整型元素的距离。这包括 a[2]、a[3]、a[4]、a[5] 和 a[6]。

(四)两个相同类型的指针可以相互赋值

#include <stdio.h>

int main()
{
    int a = 100;
    int *p, *q;
    p = &a;
    printf("a = %d %d\n", a, *p);
    q = p;
    printf("*q = %d\n", *q);
    *q = 999;
    printf("a = %d\n", a);

    return 0;
}

执行结果:

a = 100 100
*q = 100
a = 999

八、指针数组

(一)指针和数组的关系

  1. 指针可以保存数组元素的地址
  2. 定义一个数组,数组中有若干个相同类型指针变量,这个数组被称为指针数组。它是若干个相同类型的指针变量构成的集合。

(二)指针数组的定义方法

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

int * p[10];//定义了一个整型的指针数组 p,有 10 个元素 p[0]~p[9],每个元素都是 int *类型的变量
int a;
p[1]=&a;
int b[10];
p[2]=&b[3];

(三)指针数组的分类

字符指针数组、短整型指针数组、整型的指针数组、长整型的指针数组
float 型的指针数组、double 型的指针数组
结构体指针数组、函数指针数组

大多数情况下,指针数组都用来保存多个字符串。

#include <stdio.h>

int main()
{
    char *name[5] = {"Xing 9", "Greatwall", "baisc", "Beijing", "Computer"};
    int i;
    for (i=0;i<5;i++)
    {
        printf("%s\n", name[i]);
    }

    return 0;
}

执行结果:

Xing 9
Greatwall
baisc
Beijing
Computer

九、指针的指针

在C语言中,一个指针是一个变量,它存储了另一个变量的地址。例如,如果我们有一个整数变量 int a,那么 int *p 可以是一个指向 a 的指针。

现在,当我们谈论指针的指针时,我们实际上是在说一个指针,它指向另一个指针。这意味着我们有一个指向指针的指针变量。例如,如果我们有一个指向整数的指针 int *p,那么 int **pp 就是一个指向 p 的指针。

#include <stdio.h>

int main()
{
    int a = 666;
    int *p = &a;
    int **pp = &p;

    // 使用指针的指针来访问和修改整数a的值
    printf("Value of a: %d\n", a);
    printf("Value of a using p: %d\n", *p);
    printf("Value of a using pp: %d\n", **pp);

    **pp = 20;
    printf("Value of a: %d\n", a);
    return 0;
}

执行结果:

Value of a: 666
Value of a using p: 666
Value of a using pp: 666
Value of a: 20

指针的指针在处理复杂的数据结构(如链表、树等)时非常有用,尤其是在需要操作这些结构的指针时。理解指针的指针对于深入掌握C语言至关重要。

在C语言中,指针变量的大小取决于它所在的系统架构。通常,在32位系统上,一个指针变量占4个字节;在64位系统上,一个指针变量占8个字节。这是因为指针变量存储的是它所指向内存的地址,而地址的大小取决于系统的地址总线宽度。

同样地,指针的指针(即指向指针的指针)在32位系统上也是占4个字节,在64位系统上占8个字节。这是因为指针的指针只是存储了另一个指针的地址,其大小与普通指针相同。

这里的关键点是,指针的大小与它所指向的数据类型无关,而与系统的架构有关。无论是指向整数、字符、结构体还是指针,指针变量的大小在同一个系统架构下都是一样的。

例如,假设我们有一个指向整数的指针int *p,和一个指向指针的指针int **pp,在一个64位系统上,p和pp都会占8个字节。

#include <stdio.h>

int main()
{
    int a = 666;
    int *p = &a;
    int **pp = &p;

    printf("sizeof(a): %d\n", sizeof(a));
    printf("sizeof(p): %d\n", sizeof(p));
    printf("sizeof(pp): %d\n", sizeof(pp));

    return 0;
}

执行结果:

sizeof(a): 4
sizeof(p): 8
sizeof(pp): 8

再举个例子:

int a = 666;
int *p = &a;
int **q = &p;
int ***m = &q;

*p == a**q == *p == a
***m == **q == *p == a
一级指针保存普通变量的地址,二级指针保存一级指针的地址,以此类推。

十、字符串和指针

字符串:以’\0’结尾的若干的字符的集合
字符串的存储形式:数组、字符串指针、堆分配

  1. 数组
    char string[100] = "I love C!";创建了一个可以存储100个字符的数组。这个数组在栈上分配内存。数组实际上使用了 10 个字符的空间(包括字符串的9个字符和结束符)。
  2. 字符串指针(一般不用)
    char *str = "I love C!";创建了一个指向字符串常量的指针。这个字符串常量存储在只读的内存区域,通常是程序的.data或.rodata段。这种方式下,你不能修改字符串的内容,因为尝试修改只读内存会导致运行时错误。str 只是存放了字符 I 的地址编号。
  3. 堆分配
char *str =(char*)malloc(10*sizeof(char));//动态申请了 10 个字节的存储空间,
首地址给 str 赋值。
strcpy(str,"I love C")//将字符串“Ilove C!”拷贝到 str 指向的内存里

**可修改性:**栈和全局区内存中的内容是可修改的;文字常量区里的内容是不可修改的;堆区的内容是不可修改的。
str 指针指向的内存能不能被修改,要看 str 指向哪里。
str 指向文字常量区的时候,内存里的内容不可修改
str 指向栈、堆、静态全局区的时候,内存的内容是可以修改

初始化:
字符数组、指针指向的字符串:定义时直接初始化

char buf_aver[]="hello world";
char *buf_point="hello world";

堆中存放的字符串:不能初始化、只能使用 strcpy、scanf 赋值

char *buf_heap;
buf_heap=(char *)malloc(15);
strcpy(buf_heap,"hello world");
scanf(%s”,buf_heap);

使用时赋值
字符数组:使用 scanf 或者 strcpy

char buf_aver[128];
buf_aver="hello kitty";//错误,因为字符数组的名字是个常量
strepy(buf aver,"hello kitty");//正确
scanf("%s" buf aver);//正确

指向字符串的指针:

char *buf point;
buf point="hello kitty";//正确,buf point指向另一个字符串
strcpy(buf point,"hello kitty");//错误,只读,能不能复制字符串到 buf piont 指向的内存里,取决于 buf point指向哪里。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值