C语言入门笔记整理 05 指针

本文深入探讨了C语言中的指针,包括指针的定义、作用、分类以及动态内存分配。指针是C语言的核心,它可以表示复杂数据结构、快速传递数据、返回多个函数值。文中通过实例解释了指针变量、指针运算、数组与指针的关系,以及动态内存分配的原理和方法。动态内存分配允许在程序运行时动态创建数组,解决了传统数组长度固定的问题。文章还讨论了多级指针的使用,展示了如何通过指针在函数间共享和修改数据。
摘要由CSDN通过智能技术生成

指针

指针就是地址, 内存单元的编号

#include <stdio.h>
int main(void)
{
    int * p; // p是变量名,int * 表示p变量存放的是 int类型变量的地址
    int i = 3;
    
    p = &i;
            /**
         p保留了i的地址,因此 p 指向 i。
         p不是 i,i 也不是p。修改p的值,不影响i的值。
         */
//    p = i; // error: 类型不一致,p只能存放int类型变量的地址,不能存放int类型变量的值
//    p = 55;  error: 原因同上
    
    return 0;
}

p 指针变量: 可以存放变量 的 地址。
i 普通变量: 只能存放 变量的 值。

*如果一个指针变量指向了某个普通变量,则 指针变量就完全等同于普通变量

如果p是个指针变量,并且p存放了普通变量i的地址则p指向了普通变量i
*p 就完全等同于 i
或者说:在所有出现*p的地方都可以替换成i。
在所有出现i的地方都可以替换成*p。
p是用来存放要读取数据的地址。
*p是让编译器从指定的地址中读取出数据。
p输出一个指针的地址,通常是输出一个16进制的数。
*p表示 指针所指向的内存地址中 存放的内容。

指针就是 地址,
地址 就是 指针。

地址 就是内存单元 的编号。
指针变量 是 存放地址的变量。

指针 和 指针变量 是两个不同的概念
但是要注意,通常我们叙述时候,会把指针变量 简称为指针。
但实际含义并不一样。

指针作用

表示一些复杂的数据结构
快速传递数据
使函数能返回一个以上的值
能直接访问硬件
能够方便的 处理字符串
是 理解 面向对象语言中 引用 的基础

总结:
指针是C语言的灵魂。

指针的定义

地址:内存单元的编号。
一个从0开始的非负整数。
范围: 4G 为例
图解
CPU 和 内存 条 之间 有 三根线。

控制线
数据线
地址线

内存条分为很多很多格子

先把内存条 数据 读入 CPU
CPU 内部处理
处理完结果写入内促条
内存条内数据 写入硬盘

控制线: 可读,可写,控制数据传输的方向
数据线:进行数据的传输
地址:确定(在内存条上)需要控制的单元地址

一根线 有两种状态 0 和 1 可以控制2个内存单元
两根线 可以控制 4个内存单元
3根线 2的3次方

一般是32位,就是32根地址线,能控制 2的32次方个单位,一个单元对应8个位。 2的32次方 X 8 个位
1K = 1024 B = 2 的10次方个单元
1MB = 1024 KB = 2的20次方
1G = 1024MB = 2的30次方
2的32次方 也就是 4 G

2的32次方 是

指针的分类

指针使用例子

#include <stdio.h>
int main(void)
{
    int i = 5;
    int * p;
    int * q;
    
    p = &i;
    q = p;
    
    printf("%d\n",*q);
    return 0;
}

p q r 都指向同一个地址
free§ 释放指向空间的地址
q和r不能free了

经典交换例子:


#include <stdio.h>
int main(void)
{
    int a = 3;
    int b = 5;
    int t;
    
    t = a;
    a = b;
    b = t;
    
    
    printf("a = %d, b = %d\n", a, b);
    return 0;
}

a = 5, b = 3
#include <stdio.h>

void exchange(int *, int *);

int main(void)
{
    int a = 3;
    int b = 5;
    int t;
    
    int * p = &a;
    int * q = &b;
    
    printf("a = %d, b = %d\n", a, b);
    exchange(p, q);
    printf("a = %d, b = %d\n", a, b);
    return 0;
}

void exchange(int * a, int * b){
    int t = *a;
    *a = *b;
    *b = t;
}

星号的含义

1. 乘法
2. 定义指针变量
	
	int * p;
	
	定义一个名字叫做p的变量, int * 表示p只能存放 
	
	如果该运算符放在  已经定义好的指针变量  的前面
	如果p是一个已经定义好的指针变量则 
	*p表示 以p的内容为地址的变量

如何通过 被掉函数 修改 主调函数的 普通变量 的值

1. 实参 必须为该 普通变量的地址
2. 形参 必须是 指针变量
3. 在被掉函数中 通过   *形参名=... 
		的方式 对主调函数相关变量的值进行修改

指针和数组的关系

指针和一维数组

#include <stdio.h>

int main(void)
{
    int a[5];     // a是数组名 5是元素的个数   元素 就是变量 a[0] --- a[4]
    int b[3][4];  // 3行4列 a[0][0]是第一个元素, a[i][j]是第i+1行 j+1列元素。
    return 0;
}

一维数组名 是一个 指针常量
它存放的是 一维数组的 第一个元素的地址。
or
数组名是一个指针常量,表示数组第一个元素的的起始地址。

①用数组下标引|用数组元素数组a中元素用下标表示为:

a[0] a[1] a[2] a[3] a[4]

②用指针引用数组元素

数组a中元素用下标表示为:

int *p = a;

*p, *(p+1)*(p+2)*(p+3)*(p+4)

a 就是 a[0]的地址。
数组元素a[i] 就是当作 *(a+i)去处理的

  int a[5];

  int *p = a; 

  可以 ++p 递增指针p指向下一个数组元素,然后用*p取得元素的值。

  能不能用a++或者++a把指针指向下一个数组元素? 不能!!! 开头就说过,数组名是指向数组首元素的指针常量。指针a是不可以指向其他元素的,只能指向首元素的起始地址。


#include <stdio.h>

int main(void)
{
    int a[5];
    printf("%#X\n", &a[0]);
    printf("%#X\n", &a);

    return 0;
}
0XBFEFF2E0
0XBFEFF2E0

如果 一个 函数 需要对 一维数组 进行操, 那么它 需要 几个参数 ,用来处理信息。

两个参数: 数组第一个元素 首地址; 数组的长度

#include <stdio.h>

void f(int *, int);

int main(void)
{
    int a[5] = {1,2,3,4,5};
    int b[6] = {-1,-2,-3,-4,99};
    int c[100] = {1,99,22,33};
    
    f(a,5);
    f(b,6);
    f(c,100);
    
    return 0;
}


void f(int * pArr, int len){
    int i;
    for (i = 0; i < len; i++) {
        printf("%d ", *(pArr + i));
    }
    printf("\n");
}
1 2 3 4 5 
-1 -2 -3 -4 99 0 
1 99 22 33 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 

通过指针修改数组内元素

#include <stdio.h>

void f(int *, int);

int main(void)
{
    int a[6] = {1,2,3,4,5,6};
    f(a, 6);
    return 0;
}


void f(int * pArr, int len){
    int i;
    pArr[3] = 88;

    for (i = 0; i < len; i++) {
        printf("%d ", *(pArr + i));
    }
    printf("\n");
}
1 2 3 88 5 6 
int main(void)
{
    int a[5] = {1,2,3,4,5};
//    a = &a[2]; // error 因为a是常量,不可以改变的
    printf("%#X, %#X\n", a, &a[0]); // a 和 a[0]是同一个地址
    return 0;
}
指针变量的运算
指针变最不能相加不能相乘也不能相除
如果两个指针变量指向的是同一块连续空间中的不同存储单元, 则这两个指针变量才可以相减

#include <stdio.h>

int main(void)
{
    int i = 5;
    int j = 10;
    int * p = &i;
    int * q = &j;
    int a[5];
    
    p = &a[1];
    q = &a[4];
    
    printf("p和q所指向的单元间隔%d个单元\n", q-p);
}

p和q所指向的单元间隔3个单元
一个指针变量到底占几个字节
一个指针变量,无论它指向的变量占几个字节该指针变量本身只占四个字节

一个变量的地址使用该变量首字节的地址来表示

假设 p 指向char类型变量(1个字节)
假设 q 指向int类型变量(4个字节)
假设 r 指向double类型变量(8个字节)

p q r本身所占的字节数是否一样

答案:pq r 本身所占的字节数是一样的

sizeof(数据类型)
功能:返回值 就是 该数据类型所占的字节数。

sizeof(int) = 4
sizeof(char) = 1
sizeof(double) = 8

一个指针变量,无论 它指向的那个类型 占几个字节
它自己本身 都只占8个字节

#include <stdio.h>

int main(void)
{
    char ch = "A";
    int i = 99;
    double x = 66.6;
    char * p = &ch;
    int * q = &i;
    double * r = &x;
    
    printf("%d %d %d\n", sizeof(p), sizeof(q), sizeof(r));
    return 0;
}
8 8 8

CPU和内存间
1根线 2(因为有0和1两种状态)
2根线 2的2次方
3根线 2的3次方

32根线 2的32次方
2的30次方 * 2的2次方 == 4G
内存条大小4G

编号: 从零开始,到最后一个,用32根线表示
000…0000 一共32个0,用来表示第一个地址
111…11111 一共32个1,用来表示最后一个地址

2 cpuj接收到32位地址总线的数据是32位的数据,也就是最大4字节的数据,所以统一4字节
我们只需要4个字节就可以找到所有的数据。所以,在32位的计算机中,指针占4个字节。同理,在64位的计算机中,指针占8个字节。

计算机不会像人一样思考,人可以根据你地址的大小来找适当的牌子写地址,计算机无论你地址大还是小,他都会用自己最大的牌子来写地址,而32位系统加32位软件能写地址的最大的牌子就是32位,也就是4个字节。

动态内存分配

传统数组的缺点:

1.数组长度必须事先制定,且只能是常整数,不能是变量
例子:	
	int al5]: //OK
	
	int len = 5; int a[len];  //error

2.传统形式定义的数组,该数组的内存, 程序员无法手动释放,直到函数结束,自动释放。

3.数组的长度不能在函数运行的过程中动态的扩充or缩小。
数组的长度一旦定义,其长度就不能再改变了。

4.A函数定义的数组,在A函数运行的期间可以被其它函数使用。
但是A函数运行完毕以后,A函数中的数组将无法在被其他函数使用。
传统方式定义的数组,不能跨函数使用
int main(void)
{
    int a[5] = {1,2,3,4,5}; // 20个字节的存储空间,程序员无法手动编程释放它, 它只能在本函数运行完毕时由系统自动释放。
    return 0;
}

动态内存分配举例

第一个字节地址本身无意义,因为你连它是什么类型都不知道,这时候要强制转换成有实际意义的地址,可以让人们使用,看得懂的

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

int main(void){
    int i = 5; // 分配了4个字节,静态分配
    int * p = (int *)malloc(4);// 12行
    /*
     1. 要使用malloc函数,必须添加 <stdlib.h> 这个头文件
     2. malloc函数 形参: 只有一个形参,并且是int类型
     3. 4表示 请求系统为 本程序分配四个字节
     4. malloc函数只能返回第一个字节的地址
     但是p指向的变量所占的字节数并未确定
     所以需要 int* 强制类型转换,将其转化为整型变量指针的类型 后面还有4个字节
     p指向了4个字节,但是p本质上只保留了第一个字节的地址。
     5. 这一行代码最终分配12个字节,p变量占8个字节,p指向的内存也占4个字节。
     6. p本身所占的内存是静态分配的,p所指向的内存是动态分配的。p本身的内存只能在p变量所在的函数运行终止时由系統自动释放
     */
    
    *p = 5; //*p代表的就是一个int变量,只不过*p这个整型变量的内存分配方式和11行的i变量,分配方式相同。
    free(p); // 表示把p所指向的内存给释放掉
    printf("同志们好!\n");
    return 0;
}

在这里插入图片描述

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

void f(int *);

int main(void){
    int * p = (int *)malloc(sizeof(int)); // sizeof(int) 返回值是int所占的字节数
    *p = 10;
    
    printf("%d\n", *p); //10
    f(p);
    printf("%d\n", *p); //200
    
    return 0;
}

void f(int * q){
//    *p = 200; //error
    *q = 300;
}

静态分配内存 和 动态内存的比较
#include <stdio.h>
#include <stdlib.h>

int main(void){
    int a[5]; // 如果int占4个字节的话,则本数组一共包括20个字节,每4个字节 为一个 int变量
    int len;
    int *pArr; // pArr存放了第一个字节的地址,但指向了整体前四个字节
    int i;
    
    printf("请输入你要存放的元素的个数:");
    scanf("%d\n", &len);
    // 动态构造一维数组
    pArr = (int *)malloc(4 * len); // 动态构造一个一维数组:创造了4*len个字节,pArr是指向前4个字节 , 这是类似 我们写一个 int pArr[len]
//    pArr[0]
//    pArr[1] 加1就是加4个字节,加4是因为定义的是int类型
    
    // 对动态一维数组赋值
    for (i = 0; i < len ; i++) {
        scanf("%d", &pArr[i]);
    }
    
    // 动态输出一维数组
    for (i = 0; i < len; i++) {
        printf("%d\n", pArr[i]);
    }
    
    free(pArr); //释放动态分配的数组
    return 0;
}

pArr【i】 = *(pArr + i);

int main(void){
    int a[5]; // 如果int占4个字节的话,则本数组一共包括20个字节,每4个字节 为一个 int变量
    int len;
    int *pArr; // pArr存放了第一个字节的地址,但指向了整体前四个字节
    int i;
    
    printf("请输入你要存放的元素的个数:");
    scanf("%d\n", &len);
    // 动态构造一维数组
    pArr = (int *)malloc(4 * len); // 动态构造一个一维数组:创造了4*len个字节,pArr是指向前4个字节 , 这是类似 我们写一个 int pArr[len]
//    pArr[0]
//    pArr[1] 加1就是加4个字节,加4是因为定义的是int类型
    
    // 对动态一维数组赋值
    for (i = 0; i < len ; i++) {
        scanf("%d", &pArr[i]);
    }
    
    // 动态输出一维数组
    for (i = 0; i < len; i++) {
        printf("%d\n", *(pArr + i));
    }
    
    free(pArr); //释放动态分配的数组
    return 0;
}

静态内存 由 系统自动分配,由系统自动释放。
静态内存 在 栈 分配。

动态内存是 由 程序员手动分配,手动释放动
态内存是 在 堆 分配的。

多级指针



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

int main(void){
    int i  = 10;
    int * p = &i;
    int ** q = &p;
    int *** r = &q;
    
    // r = &p; //error: 因为r是int***类型,r只能存放int **类型变量的地址
    printf("i = %d\n", ***r);
    return 0;
}





在这里插入图片描述


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

void f(int ** q){
        //*q就是p
    **q = 11;
}

void g(){
    int i = 10;
    int * p = &i;
    
    f(&p); // p是int * 类型, &p是int **类型
    printf("%d\n", i);
}

int main(void){
    g();
    return 0;
}
11
#include <stdio.h>
#include <stdlib.h>

void f(int ** q){ // q是个指针变量,无论q是什么类型的指针变量,都只占8个字节
        
    int i = 5;
    // *q就是p  q和 **q 都不等于p
    // *q = i; // error 因为*q = i; 等价于 p = i; 这样写是错误的
    *q = &i; // p = &i;
//    **q = 11; // 这样没问题,正确
}

int  main(){
    int * p;
    
    f(&p); // p是int * 类型, &p是int **类型
    printf("%d\n", *p); // 本句语法没有问题, 但是逻辑有问题。
    
    return 0;
}
5
#include <stdio.h>
#include <stdlib.h>


void f(int ** q){
    *q = (int *)malloc(sizeof(int)); // sizeof(数据类型) 返回值是该数据类型所占的字节数
    // 等价于 p = (int *)malloc(sizeof(int));
//    q = 5; // error
    
//    *q = 5; // p = 5
    
    ** q = 5; // *p = 5
    
}

int main(){
    int * p;
    f(&p);
    printf("%d\n",*p);
    
    free(p);
    return 0;
}

/*
 动态分配内存不消失就是q的地址不消失,
 q的地址不消失,
 地址上存储的内容也不会消失,
 也就是p不消失,
 也就是p的地址不消失,
 p的地址不消失上面保存的内容也不会消失
 
 */

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值