C语言 第7节 指针

内存中是一个一个单元,每个单元每8位(01010101)给一个编号,这个编号就是地址。
指针即地址,即内存单元的编号。

#include <stdio.h>

int main(void) {
  #p是变量的名字,int * 表示p变量存放的是int类型变量的地址
  int * p;
  int i = 3;
  /**
    将i的地址放入p变量中
    p保存了i的地址,因此p指向i,p不是i,i也不是p
    修改p的值不影响i的值,修改i的值也不影响p的值
    */
  p = &i; 

  p = i; //这是错的,p只能存放int类型数据的地址,不能存放int类型的值
  p = 55; //error ,原因同上

  return 0;
}

// *p就是以p的内容为地址的变量
void pointer(void) {
  int a = 3;
  int * p = &a;
  int b = *p; //*p = a
  printf("a = %d, *p = %d, b=%d",a, *p, b);
}

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

例子:


如果p是个指针变量,并且p存放了普通变量 i 的地址,则p指向了普通变量 i
*p 完全等同于 i
或者说:
在所有出现*p的地方都可以用 i 替换
在所有出现i的地方都可以用*p替换

int *p;

  • p是指针变量名,int * 表示p变量存放的是int类型的地址
  • 不表示定义一个名字叫 *p 的变量
  • p是变量名,p变量的数据类型是 int * 类型
    所谓int * 类型就是存放int类型数据地址的类型


指针的概念

指针就是地址,地址就是指针,地址就是内存编号
地址就是内存单元(8位一字节即一个单元)的编号
指针变量:存放指针地址的变量
指针指针变量 是2个不同的概念。
但是要注意:我们通常叙述时通常把指针变量简述成指针,实际上他们不一样。
指针的本质:是一个操作受限的非负整数,不能相加,不能相乘,不能相除,能相减,相减的值即:两个地址之间的间隔。


指针的重要性
  1. 表示一些复杂的数据结构
  2. 快速地传递数据
  3. 使函数返回一个以上的值
  4. 能直接访问硬件(指针即内存地址)
  5. 能否方便地处理字符串
  6. 理解面向对象语言中引用的基础

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

指针的定义
  1. 什么是地址?
  • 内存单元的编号
  • 从0开始的非负整数
  • 范围:4G内存()


    cpu和内存条之间有控制总线、数据总线、地址总线

控制总线
1根控制总线根据高低电频,有2个状态 :0 1,能控制2个内存单元:2字节:2 * 8 = 16 位
32位OS:32根控制总线:即 232 个状态。能控制 232 个内存单元。 232 * 8 位。

1KB = 210B(字节)
1MB = 210KB = 220B
1G = 210MB = 230B

232 = 230B x 22 = 1G * 4 = 4G
所以32位系统最大支持4G内存


指针的分类


1. 基本类型指针

下面的程序修改了一个未知地址的值=5,可能会引起其他程序崩溃。

#include <stdio.h>

int main(int) {
 # p未初始化,是一个垃圾值,垃圾值的值不确定,对应不确定的的内存地址
  int * p;
  int i = 5;
#将5赋值给这个不确定的内存地址
  *p = i; 
  printf("%d",*p);
}

void usualError(void) {
  int i = 5;
  int * p;
  int * q;
  p = &i;

#下面这句错误啦,*q是整数类型,p是int * 类型,类型不匹配
  *q = p;

# 错误,*p 等于其存储地址中的值:5
#  *q 未初始化,按其垃圾值(未知)作为地址,并将此地址中的值变为5
  *q = *p; 

# q是垃圾值,p的值也变成了垃圾值
  p = q; 
  printf("%d\n", *q);
}

---------------------------------------------------------------
q是属于本程序的,所以本程序可以读取写q的内容,
但是如果q的内部是垃圾值,则本程序不能读取*q的内容。
因为*q所代表的内存单元的权限并没有分配给本程序
所以连读取*q都不行:print("%d",*q),野指针
---------------------------------------------------------------

多指针指向统一地址空间,用完需要释放

#include <stdlib.h>
void freePoint(void) {
  int i = 5;
  int * a = &i;
  int * b = &i;
  int * c = &i;
  ......
  free(a);  //只释放一次就行,再释放下面两行就有问题
//  free(b);
//  free(c);
}

程序报错了

free()只是将malloc()函数申请的空间释放掉,并不能将指针置空,指针的指向还是之前的,并不会改变,所以用指针与NULL比较作为循环的结束条件在双链表之中是不适合的,会导致死循环的产生。
free()只能对malloc申请的空间进行一次释放,第二次释放会出现错误

内存泄漏:
内存只使用不释放,可用内存越用越少,当为0的时候调用虚拟内存,虚拟内存也用光了就崩溃了。

附注:星号的3种含义
(1)乘法
(2)定义指针变量
   int * p;
   定义了一个名字为p的变量,int * 表示只能存储int类型变量的地址。

(3)指针运算符
该运算符放在已经定义好的指针变量前面
如果p是一个已经定义好的指针变量,则*p表示以p内容为地址的变量

//以下三种写法均可
int * p;
int *p;
int* p;
char A = 'A';
p = &A; //类型不匹配,错误

实参和形参
永远是不同的变量

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

void test(int a, int b) { //a,b是局部变量,只在test()中使用
    int t;
    t = a;
    a = b;
    b = t;
}

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

int main(void) {
    int a = 3, b = 5;   //a,b是局部变量,只在main()中使用
    test(a,b); //这里只是改了形参的值,
    printf("a = %d,b = %d\n",a,b);  //a = 3,b = 5
    swap(&a,&b);
    printf("a = %d,b = %d\n",a,b);  //a = 5,b = 3
    return 0;
}
总结:如何通过被调函数修改主调函数的值?
  1. 实参必须为改普通变量的地址
  2. 形参必须为指针变量
  3. 在被调函数中通过: *形参名 =
    的方式就可以修改主调函数相关变量的值。

指针使函数返回一个以上的值

2. 指针和数组

  • 指针和一维数组
    • 一维数组名是个指针常量
    • 它存放着一维数组第一个元素的地址
# include <stdio.h>

int main(void) {
    int a[5];
    int b[5];

    //a = b; //这是错的,a是常量,代表a[0]的地址
    printf("%#X\n", &a[0]);     //0X19FF2C
    printf("%#X\n",a);          //0X19FF2C   输出与上面相同
}
  • 下标和指针的关系
# include <stdio.h>

void changeArrElement(int * arr, int len) {
   //arr:形参,c:实参
   //arr[2] == *(&arr[0]+2)  == *(arr + 2) == *(c+2) = c[2] 
   arr[2] = 10; 
   for(int i = 0; i<len; i++)
       printf("c[%d] = %d\n",i,arr[i]);
}

int main(void) {
   int c[5] = {1,2,3,4,5};
   printf("c[2]=%d\n" , c[2]);  //c[2]=3
   changeArrElement(c , 5);
   printf("c[2]=%d\n" , c[2]);  //c[2]=10
   return 0;
}
  • 指针变量的运算
    指针变量不能相加,不能相乘、不能相除,只能相减。
    如果2个指针变量指向的是同一块儿连续空间中的不同存储单元,则可以相减
int a = 1;
int b = 2;
int * i = &a;
int * j = &b;

//p - q没有任何意义,因为空间不保证连续
int a[5] ;
i = &a[1];
j = &a[4];
printf("i 和 j 所指向的单元相隔 %d 个单元 \n", (j - i));
  • 一个指针变量到底占几个字节
    • 假设p 指向char类型变量(1个字节)
    • 假设q 指向int类型变量(4个字节)
    • 假设r 指向double类型变量(8个字节)
      p q r 本身所占的字节是否相同?

sizeof(数据类型)
功能:返回值就是该数据类型所占的字节数
例子:sizeof(int) = 4 sizeof(long)

sizeof(变量名)
功能:返回值是该变量所占的字节数

void sizeofAddress() {
    int i = 10;
    double j = 20.0;
    char k = 'K';
    int * ii = &i;
    double * jj = &j;
    char * kk = &k;
    // 结果是 4, 4, 4,表示都是4个字节
    printf("%d, %d, %d \n", sizeof(ii), sizeof(jj), sizeof(kk));
}

一个字节一个编号,即8个01由一个编号表示,而不是1位一个编号对外表示。一个字节里面有8个位,取哪个位的编号作为这个字节的编号呢?首位!

总结
一个指针变量,无论它指向的变量占几个字节,该变量本身只占4个字节。一个变量的地址是用该变量首字节的地址表示

CPU控制内存有32根地址总线,一根线有2个状态:0 和1,32根线就有:
232个状态,记录232个字节的内存地址。
第一个地址用二进制表示就是232个0,全是0
最後一个地址用二进制表示就是232个1,全是1

专题:动态内存分配
(1)传统数组的缺点
1.数组的长度不能通过变量的形式指定

int a[5]; //OK
int lent = 5; int a[len]; /error
  1. 传统形式定义的数组,该数组内程序员无法手动释放
    在一个函数运行期间,系统为该函数中的数组分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放
  2. 数组的长度一旦定义,其长度不能更改。
    数组的长度不能再函数运行过程中动态地扩充或缩小
  3. 传统方式定义的数组不能跨函数使用
    A函数定义的数组,在A函数运行期间可以被其他函数调用,但A函数运行完毕之后,A函数的中的数组将无法被其他函数调用

(2)为什么需要动态内存分配
动态数组很好地解决了传统数组的4个缺陷,传统数组也叫静态数组。
malloc : memory(内存) allocate (分配)

  1. 添加malloc.h头文件
  2. malloc(int size)函数只有一个形参,是整形
  3. malloc(4) : 请求系统为本程序分配4个字节
  4. malloc(size) 函数只能返回第一个字节的地址
  5. 前面需要加强制类型转换, 因为不知道
  6. malloc(100) 请求系统为程序分配100个字节,但是只能返回第一个字节地址,而仅仅知道第一个字节的地址无法知道此变量总共占几个字节,所以需要在前面加一个强制类型转换,告诉别人第一个字节的地址到底是什么类型的地址
  7. p 变量本身是静态分配的,p所指向的内存所malloc动态分配的
# include <stdio.h>
# include <malloc.h>

int main(void) {
  int i = 5; //分配了4个字节,静态分配
//返回值类型是第一个字节的地址,其地址是int类型的地址
  int * p = (int *) malloc(4); 
//上面的语句分配了8个字节,p分配4个字节用以装地址,malloc函数又动态分配了4个,4 + 4 = 8

 
 //*p代表的就是一个整形变量,只不过*p的内存分配方式和int p = 5; 的分配方式不同
  *p = 5;

 //free只能释放动态内存,静态内存只能由系统来释放,不能手动释放
  free(p); //将p指向的内存释放掉。
}
# include <stdio.h>
# include <malloc.h>

void f(int * q) {
    * q = 200;
    //将q所指向的内存释放掉,这样的话下面的printf就出错了
//  free(q); 
}

int main(void) {
    int * p = (int *)malloc(sizeof(int)); //
    *p = 10;
    printf("%d\n", *p);
    f(p);
    printf("%d\n", *p);
    return 0;
}
int类型指针存放首字节地址
free()就叉掉
image.png
void f(int * q) {
    * q = 200;
    //将q所指向的内存释放掉,这样的话下面的printf就出错了
//  free(q); 

    int a[5];   //int类型变量占4个字节,本书组总共包含20个字节,每4个字节被当成一个int来使用
    int len;
    int * pArr;
    int i;

    //动态地构造一维数组
    printf("请输入你要存放元素的个数:");
    scanf("%d", &len);
    //动态地构造了一个一维数组,长度是len,
    pArr = (int *)malloc(4 * len);
    for(i = 0; i < len; i++){
        scanf("%d", &pArr[i]);
    }
    
    printf("一维数组的内容是:\n");
    for(i = 0; i < len; i++){
        printf("%d\n", pArr[i]);
    }

    realloc(pArr,sizeof(int) * 10); //扩充至4 x 10个字节;缩小的话,保留前面的数据 ,未执行成功,

    free(pArr);
}

(3)动态内存分配举例,动态数组的构造
(4)静态内存和动态内存的比较

  1. 静态内存是分配在栈中,动态内存是分配在堆中。
  2. 栈是一种存储结构,而堆不是一种存储结构,是分配内存排序方式。
  3. 静态内存是由系统自动分配,由系统自动释放;动态内存写malloc()代码创建,free()代码手动释放


    方法调用是压栈,调用完出栈

(5)跨函数使用内存的问题

  • 静态内存是在栈里,压栈,函数执行完了就出栈,就没了释放了,所以函数内部的变量不能够跨函数使用
  • 动态内存:不在栈中,在堆里。不涉及压栈出栈,可以跨函数使用。

3. 多级指针

# include <stdio.h>
# include <malloc.h>

void rr(int *** r) {
    ***r = 30;
}

void g(int ** q) {
    **q = 20;
    int *** r = &q;
    rr(r);
}

void f(int * p) {
    *p = 10;
    int ** q = &p;
    g(q);
}

void test() {
    int * p = (int *)malloc(sizeof(int));
    f(p);
    printf("%d\n", *p);
}

int main(void) {
    int i = 10;
    int * p = &i;
    int ** q = &p;
    int *** r = &q;
    
    printf("%d\n" , ***r);
    printf("%d\n" , **q);
    printf("%d\n" , *p);
    printf("%d\n" , i);
    test();
    return 0;
}

多级指针图解

4. 指针和函数
静态变量不能跨函数使用

# include <stdio.h>
# include <malloc.h>

void f(int ** q) {
    int i = 10;
等价于p = &i; 而i的地址是f()的局部变量,
而且是静态变量,栈中的,非堆中的。
所以main()能保存和指向变量i的地址,
但是等f函数执行完后,i变量的地址就被释放了
所以访问的结果是错误的。
    *q = &i; 
}

正确版本
void f(int ** q) {
    *q = (int *)malloc(sizeof(int));
    **q = 20;
}

int main(void) {
    int * p;

    f(&p);
本语句语法没问题,但逻辑有问题:它访问了一个它没权限访问的地址
    printf("%d\n",*p); 
    return 0;
}

5. 指针和结构体

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容