C语言 第7节 指针

作者: 小超_8b2f | 来源:发表于2019-07-06 23:12 被阅读0次

内存中是一个一个单元,每个单元每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. 指针和结构体

相关文章

  • C语言05- 指针

    C语言05- 指针 13:指针 指针是C语言中的精华,也是C语言程序的重点和难点。 13.1:指针定义与使用 指针...

  • 02-C语言的指针

    02-C语言的指针 目标 C语言指针释义 指针用法 指针与数组 指针与函数的参数 二级指针 函数指针 指针在C中很...

  • C语言指针总结大学霸IT达人

    C语言指针总结大学霸IT达人 C语言的指针是C语言区别其它语言的最主要的特定之一。有了指针,C语言就可以抛开所有束...

  • 带小白学C语言指针

    C语言里指针才是C语言的开始和指针;C语言里基本所有东西都是由指针演变而成; 指针是指向地址的变量,类型就是指针...

  • 在Swift中使用C语言的指针

    在Swift中使用C语言的指针 在Swift中使用C语言的指针

  • [Swift]结构体指针操作

    C语言的指针操作 在c语言中申明一个变量并通过指针修改该变量的值 a value is 2 c语言操作结构体指针操...

  • Go语言-指针

    Go语言中的指针不同于C语言,Go语言的指针使用方法要简单很多。当然和C语言从指针定义到指针的使用都有很大的不同。...

  • C语言中的指针与数组

    C语言中的指针与数组 @(C语言)[排序算法, 快速排序, C实现] 引言 相信指针与数组是不少同学在初学C语言时...

  • 指针数组和数组指针

    指针数组|存储指针的数组 在C语言和C++语言中,数组元素全为指针的数组称为指针数组。 例如:int *p[10]...

  • iOS开发 -- C语言基础8(指针)

    iOS开发 -- C语言基础8(指针) 指针是C语言中非常重要的数据类型,如果你说C语言中除了指针,其他你都学得很...

网友评论

    本文标题:C语言 第7节 指针

    本文链接:https://www.haomeiwen.com/subject/ymvacctx.html