zoukankan      html  css  js  c++  java
  • C语言学习-Day_06


    • 学习参考B站郝斌老师的视频,文章内的源码如有需要可以私信联系。

    指针

    • 指针就是地址,地址就是指针
      • 地址是内存单元的编号(从0开始的非负整数)
    • 指针变量是存放地址的变量
    • 指针可以直接对主函数中的变量进行修改,但是需要把主函数变量的地址传给定义函数
    • 指针只能进行减运算,不能进行加乘除运算

    指针2

    • 控制线:负责控制数据是调入CPU还是调出CPU
    • 数据线:负责传输数据
    • 地址线:负责内存条中哪个内存单元编号中的数据的读取或写入

    指针概述

    • int *表示指针变量的类型
    • *p表示以指针变量为地址的值
    • &p表示指针变量存放的地址
    /*指针概述*/
    
    # include <stdio.h>
    
    int main(void)
    {
    	int * p;  //int *表示p变量存放的是int类型变量的地址,p是变量名
    	int i = 3;
    
    	p = i;  //error,p存放的是整形地址,i是整形数据
    	p = 5;  //error,原因同上
    	p = &i;  //正确,&i表示去变量i的地址
    
    	return 0;
    }
    
    • p = &i变量 p 保存了变量 i 的地址,因此 p 指向 i
    • p 不是 i ,i 也不是 p,修改值不影响彼此的值
    • 如果指针变量指向普通变量,则*指针变量就等同于普通变量
      • 所有出现*指针变量普通变量的位置都可以互换

    /*指针变量&普通变量*/
    # include <stdio.h>
    
    int main(void)
    {
    	int * p;  //指针变量,int *表示的是变量p的数据类型,p是变量名
    	int i = 5;
    	
    	p = &i;  //指针变量指向普通变量
    	printf("变量i的值是:%d
    ", i);
    	printf("变量i存放的地址是:%d
    ", &i);  //输出的是变量i存放的地址
    	printf("变量p的值是:%d
    ", p);  //输出的是变量p的值
    	printf("变量p的地址是:%d
    ", &p);  //输出变量p存放的地址
    	printf("*p的值是:%d
    ", *p);  //*p可以替换成i
    
    	i = 3;
    	printf("变量i的值是:%d
    ", i);
    	printf("变量p的值是:%d
    ", p);  //修改变量i的值,不影响变量p的值
    
    	*p = 3;  //表示指针变量p指向的值是3
    	printf("*p的值是:%d
    ", *p);
    
    	return 0;
    }
    
    /*运行结果*/
    变量i的值是:5
    变量i存放的地址是:1703720
    变量p的值是:1703720
    变量p的地址是:1703724
    *p的值是:5
    变量i的值是:3
    变量p的值是:1703720
    *p的值是:3
    Press any key to continue
    

    指针

    • 定义指针变量 p,定义整形变量 i
      • 变量 p 的地址是:1703724,变量 p 的值是变量 i 的地址,即1703720
      • 变量 i 的地址是:1703720,变量 i 的值是 5
      • *p表示 p 指向的地址存放的值,即1703720存放的值(i的值)5
      • 修改变量 i 的值,实际上 i 的存放地址没有变化,所以变量 p 也不会被影响
      • 修改变量 p 的值,p 存放的是 i 的地址,所以也不会影响 i 的值

    指针的重要性

    • 指针可以表示一些复杂的数据结构
    • 可以快速传递数据
    • 使函数返回一个以上的值
      • return只能返回一个值,返回一个值,就会结束函数
    • 能直接访问硬件
    • 可以方便的处理字符串

    指针常见错误

    /*指针常见问题*/
    # include <stdio.h>
    
    int main(void)
    {
    	int * p;
    	int i = 5;
    
    	printf("%d
    ", p);  //变量p没有初始化,使用垃圾数据(-858993460)填充
    	*p = i;
    	printf("%d
    ", *p);
    
    	return 0;
    }
    
    • 变量 p 没有被初始化,会使用垃圾数据填充
    • 系统只分配变量 p、i的地址空间,而*p = i;则是将垃圾数据对应地址空间的值进行修改,则会存在问题

    /*指针常见问题*/
    # include <stdio.h>
    
    int main(void)
    {
    	int i = 5;
    	int * m;
    	int * n;
    
    	m = &i;  //m的值为存放i变量的地址
    	*n = m;  //error,变量n没有初始化,为垃圾数据,且*n(int)和m(int *)的数据类型不同
    	*n = *m;  //error,数据类型相同,但变量n没有初始化,无法修改垃圾数据的值
    	m = n;  //n是垃圾数据,n赋值给m,m也是垃圾数据
    
    	printf("%d
    ", *n);
    	/*
    		变量n的内存单元被分配给本程序,所以可以读写n内存单元中的数据
    		但变量n没有初始化,使用垃圾数据填充,所以本程序不能读写*n的数据
    		因为*n所代表的内存单元,即垃圾数据使用的内存单元,本程序没有权限读写其中的值
    	 */
    
    	return 0;
    }
    

    指针3

    • 变量 p 没有初始化,所以系统使用垃圾数据填充
      • 所以变量 p 的地址和值都是使用垃圾数据
      • 因为垃圾数据的内存单元没有分配给本程序,所以本程序不能读取垃圾数据的内存单元编号和数值
      • 即:没有权限读写 *p的内容

    经典指针程序

    • 互换两个数字

    :指针程序,互换两个数字

    /*使用指针,互换两个数*/
    # include <stdio.h>
    
    void Exchange_1(int , int);  //函数声明,可以不用写形参
    void Exchange_2(int *, int *);
    void Exchange_3(int *, int *);
    
    void Exchange(int * p, int * q)  //正确,修改的*p,*q的值,即修改了主函数内a,b的值
    {
    	int t;  //修改*p,*q的值,则t需要定义为int类型
    
    	t = *p;
    	*p = *q;  //*p是变量p存储地址的变量值,即主函数变量a的值
    	*q = t;
    }
    
    int main(void)
    {
    	int a = 3;
    	int b = 5;
    
    	Exchange(&a, &b);  //定义函数中是int *类型,所以要取a,b的地址
    	printf("a = %d, b = %d
    ", a, b);
    
    	return 0;
    }
    
    //不能实现互换
    void Exchange_1(int a, int b)  //改变的是定义函数内的a,b的值,并没有改变主函数内的值
    {
    	int t;
    
    	t = a;
    	a = b;
    	b = t;
    
    	return;
    }
    
    //不能实现互换
    void Exchange_2(int * p, int * q)  //改变的是定义函数内的p,q的值,并没有改变主函数内的a,b的值
    {
    	int * t;  //互换p,q的值,则t需要定义为int *类型
    
    	t = p;
    	p = q;
    	q = t;
    
    	return;
    }
    
    /*运行结果*/
    a = 5, b = 3
    Press any key to continue
    
    • 星号代表的意义
      • 乘法运算
      • 定义指针变量
      • 取指针变量所代表的地址的值

    指针和数组

    • 指针和一维数组
      • 数组名是指针常量,存放的是一维数组的第一个元素的地址
      • p是指针变量,则p[i]等价于*(p + i)
      • 数组名是指针常量,不能改变
      • 指针变量指向同一块连续空间的不同存储单元,则两个指针变量可以相减
    • 指针和二维数组

    :一维数组名与数组的第一个元素的关系

    /*一维数组名与数组的第一个元素*/
    # include <stdio.h>
    
    int main(void)
    {
    	int a[5];
    	printf("%#X
    ", &a[0]);  //%#X表示十六进制输出
    	printf("%#X
    ", a);
    
    	return 0;
    }
    
    • 数组名对应的就是数组中第一个元素对应的地址
    • %d对int类型数据输出
    • %ld对long int类型数据输出
    • %f对float类型数据输出
    • %lf对double类型数据输出
    • %c对char类型数据输出
    /*运行结果*/
    0X19FF1C
    0X19FF1C
    Press any key to continue
    

    :确定一个一维数组需要几个参数

    /*确定一个一维数组需要几个参数*/
    # include <stdio.h>
    
    void f(int * p, int len)  //主函数中的a表示的是数组中第一个元素对应的地址,所以定义变量要使用int *类型
    {
    	int i;
    
    	for (i = 0; i < len; ++i)
    		printf("%d ", *(p + i));  //数组中的值存放的地址是连续的
    	printf("
    ");
    }
    
    int main(void)
    {
    	int a[5] = {-1, -2, 0, 4, 5};
    	int b[6] = {1, 2, 3, 4, 5, 6};
    	int c[10] = {1, 2, 3, 4};
    
    	f(a, 5);
    	f(b, 6);
    	f(c, 10);
    
    	return 0;
    }
    
    • 数组名表示的是第一个元素的地址,对于int类型数据,没有特殊的值标识着数组的结束,所以需要再读取数组的长度来结束数组
    • 对于char类型的数据,标识着字符的结束,所以对于char类型的数组,可以不用读取数组的长度
    /*运行结果*/
    -1 -2 0 4 5
    1 2 3 4 5 6
    1 2 3 4 0 0 0 0 0 0
    Press any key to continue
    

    /*确定一维数组需要的参数*/
    # include <stdio.h>
    
    void f(int * p, int len)
    {
    	p[3] = 5;  //p[3]等价于*(p + 3),改变的是p + 3这个地址对应的值
    }
    
    int main(void)
    {
    	int a[5] = {1, 2, 3, 4, 5};
    
    	printf("%d
    ", a[3]);  //输出4
    	f(a, 5);
    	printf("%d
    ", a[3]);  //a[3]对应的是*(a + 3),输出的是a + 3这个地址对应的值
    
    	return 0;
    }
    

    指针4

    • p和a表示的都是数组a中第一个元素的存放地址&a[0]
    • 执行函数f后,p[3]等价于*(p + 3)
      • p[3]重新赋值,即对p + 3这个地址(即a[3]的地址)存放的数据重新赋值
      • 所以执行函数后,输出a[3]的值即为函数中重新定义的值
    • p[3]a[3]代表的是同一个变量
    /*运行结果*/
    4
    5
    Press any key to continue
    

    :指针的运算

    /*指针的运算*/
    # include <stdio.h>
    
    int main(void)
    {
    	int i = 1;
    	int j = 2;
    	int * p = &i;
    	int * q = &j;
    	int a[5];
    
    	p = &a[1];
    	q = &a[4];
    	printf("q与p相隔%d个存储单元!
    ", q-p);  //指针变量在同一块连续单元的不同存储空间才可以相减
    
    	return 0;
    }
    
    /*运行结果*/
    q与p相隔3个存储单元!
    Press any key to continue
    
    • 指针变量无论指向的数据类型是什么,都只占4字节
      • char类型,占用1字节
      • int类型,占用4字节
      • double类型,占用8字节
    • sizeof(数据类型)返回值即为数据类型所占的字节数

    /*指针变量所占字节数*/
    # include <stdio.h>
    
    int main(void)
    {
    	char ch = 'A';  //字符类型数据不能省略''
    	int i = 5;
    	double x = 3.3;
    	char * p = &ch;
    	int * q = &i;
    	double * r = &x;
    
    	printf("%d %d %d
    ", sizeof(p), sizeof(q), sizeof(r));
    
    	return 0;
    }
    
    • 指针变量都是只存贮数据的第一个字节的地址,所以只占用4字节
    • 根据指针变量定义的类型,再从第一个字节向后读取相应数据类型所占用的字节数
    /*运行结果*/
    4 4 4
    Press any key to continue
    

    动态内存分配

    • 传统数组的缺点
      • 数组长度必须事先指定,且只能事长整数,不能是变量
      • 定义的数组分配的空间不能手动释放,只有在本函数运行完毕时,才会由系统自动释放
      • 数组的长度不能在函数的运行过程中动态扩充或缩小
      • 函数运行期间,函数内定义的数组可以被其他函数调用,函数终止,则该数组不能被其他函数调用
    • 为什么需要动态内存的分配
      • 动态数组解决了传统数组的缺陷
    • 静态内存和动态内存的比较
      • 静态内存由系统自动分配,由系统自动释放
      • 静态内存是在栈分配的
      • 动态内存是由程序员手动分配的,手动释放
      • 动态内存是在堆分配的
    • 跨函数使用内存的问题

    :动态数组的构造

    /*malloc函数*/
    # include <stdio.h>
    # include <malloc.h>  //使用malloc函数需要使用此头文件
    
    int main(void)
    {
    	int i = 5;  //静态分配,int类型分配了4字节
    	int * p = (int *)malloc(4);  //请求系统分配4字节
    	/*
    		int * p中p被分配的空间是静态分配的,p所指向的空间是动态分配的
    	 */
    
    	*p = 5;  //*p代表的即为一个int类型变量,地址空间是动态分配的
    	free(p);  //把p所指向的内存释放
    
    	return 0;
    }
    
    • 要使用malloc函数,必须添加malloc.h的头文件
    • malloc函数只有一个形参,形参必须时整形,代表请求系统分配的字节数
    • malloc函数只能返回第一个字节的地址
      • 所以malloc函数前必须强制指定数据类型,方便从第一个字节向后读取多少字节
    • int * p = (int *)malloc(4);中,变量p的地址空间是静态分配的,变量p指向的地址空间是动态分配的

    :malloc函数的用法

    /*malloc函数的用法*/
    # include <stdio.h>
    # include <malloc.h>
    
    void f(int * q)
    {
    	*p = 200;  //error,p是主函数中的变量,不能跨函数调用
    	q = 200;  //error,q是int *类型,只能存放地址,不能存放整形数据
    	*q = 200;  //正确,给q指向的地址重新赋值
    	free(q);  //把q指向的内存释放
    }
    
    int main(void)
    {
    	int * p = (int *)malloc(sizeof(int));  //sizeof(int)返回值是int所占的字节数
    	*p = 10;
    
    	printf("%d
    ", *p);  //输出变量p指向的值
    	f(p);  //p是int *类型,所以定义函数调用时,形参也必须是int *类型
    	printf("%d
    ", *p);  //在调用函数中,q指向的空间为动态分配的地址空间,被释放,所以不能再使用*p读取p指向的空间中的内容
    
    	return 0;
    }
    

    指针4

    • 主函数定义指针变量p,p的地址空间是静态分配的
    • p的值为动态分配的4字节int类型的存储空间
    • *p = 10表示给这个动态分配的地址空间存放一个值,为10
    • 定义函数,将p发送给指针变量q,即q也指向动态分配的地址空间
      • 在f函数中,使用* q = 200重新向这个动态分配的地址空间存放一个值,为200
      • 使用free(q);将q指向的动态分配的地址空间释放,即这个动态分配的地址空间不再存放任何值,且函数没有权限再对这个释放的空间进行读写
    • 所以不能再使用*p读取之前动态分配的地址空间的值

    :静态一维数组示例

    /*静态一维数组举例*/
    # include <stdio.h>
    
    int main(void)
    {
    	int a[5];
    	/*
    		int类型的数据占用4字节,总共5个元素,所以数组a[5]占用20字节
    		a用于存放第一个元素的地址,a[0]表示第一个元素
    		第二个元素的地址为a + 1,值为从a存放的地址向后读取4字节
    	 */
    
    	return 0;
    }
    

    :动态一维数组示例

    /*动态一维数组举例*/
    # include <stdio.h>
    # include <malloc.h>
    
    int main(void)
    {
    	int len;
    	int * p;
    	int i;
    
    	//动态构造一维数组
    	printf("请输入您想存放的元素个数:");
    	scanf("%d", &len);
    	p = (int *)malloc(4 * len);  //一个int类型元素占用4字节
    
    	//对动态一维数组进行赋值
    	for (i = 0; i < len; ++i)
    	{
    		printf("请输入第%d个元素的值:", i+1);
    		scanf("%d", &p[i]);
    	}
    	
    	//对一维数组进行输出
    	printf("一维数组的内容是:");
    	for (i = 0; i < len; ++i)
    		printf("%d, ", p[i]);
    	printf("
    ");
    
    	free(p);  //释放动态分配的数组
    
    	realloc(p,100);  //重新动态分配给变量p的地址空间是100字节
    
    	return 0;
    }
    
    /*运行结果*/
    请输入您想存放的元素个数:3
    请输入第1个元素的值:1
    请输入第2个元素的值:2
    请输入第3个元素的值:3
    一维数组的内容是:1, 2, 3,
    Press any key to continue
    
    • p存放的是数组的第一个元素的地址,后一个元素的地址为p + 1,后一个元素的值为*(p + 1)

    多级指针

    :多级指针

    /*多级指针*/
    # include <stdio.h>
    
    int main(void)
    {
    	int i = 5;
    	int * p = &i;  //p用于保存变量i的地址
    	int ** q = &p;  //q用于保存指针变量p的地址
    	int *** r = &q;  //r用于保存变量q的地址
    
    	r = &p;  //error,因为变量r的类型是int ***,不能存放变量p的地址
    
    	printf("i = %d", ***r);  //***r表示的即为变量i的值
    
    	return 0;
    }
    

    指针和函数

    • 跨函数使用内存
      • 静态变量不能跨函数使用
      • 动态变量可以跨函数使用

    :静态变量不能跨函数使用

    /*静态变量不能跨函数使用*/
    # include <stdio.h>
    
    void f(int ** q)  //q只占用4字节,存放指针变量p的地址
    {
    	int i = 5;
    	*q = &i;  //
    }
    
    int main(void)
    {
    	int * p;
    
    	f(&p);
    	printf("%d
    ", *p);  //语法正确,逻辑存在错误,权限越界
    
    	return 0;
    }
    

    指针6

    • 主函数中定义指针变量p,未初始化,将指针变量p的地址发送给函数f
    • 函数f的形参类型必须是int **,因为指针变量q存放的是指针变量的地址
    • 函数f内定义变量i,赋值为5,地址为静态分配
      • *q表示指针变量q存放的地址对应的值,即指针变量p的值,p没有指向变量,未初始化
      • 将变量i的地址赋值给指针变量q存放的地址指向的值,即p = &i
    • f函数执行完后,变量i的地址被释放,则再执行*p时,不能读写被释放的i的地址空间

    :动态内存可以跨函数使用

    /*动态内存可以跨函数使用*/
    # include <stdio.h>
    # include <malloc.h>
    
    void f(int ** q)
    {
    	*q = (int *)malloc(sizeof(int));  //sizeof返回该类型所占字节数
    	q = 5;  //error,因为q存放的是指针变量p的地址
    	*q = 5;  //error,因为*q表示指针变量p存放的垃圾数据的地址
    	**q = 5;  //正确,表示指针变量p存放的地址对应的值
    
    }
    
    int main(void)
    {
    	int * p;
    
    	f(&p);
    	printf("%d
    ", *p);  //输出*p的值,即5
    
    	return 0;
    }
    
    • 使用sizeof(int)可移植性更强,不同的机器和软件对于int类型的数据所占字节数可能不同
    • 动态分配的空间是由堆分配的,函数执行结束,地址空间不会被释放,需要手动释放free(q)
      • 静态分配的地址空间是由栈分配的,函数执行结束,该地址空间就会被释放

    以上内容均属原创,如有不详或错误,敬请指出。
    
    做别人的宝贝,别来淌我这趟浑水。
  • 相关阅读:
    Heapsort 堆排序算法详解(Java实现)
    GIve Me A Welcome Hug!
    linux系统救援模式拯救mv libc.so.6文件后无法使用命令的悲剧
    RabbitMQ集群部署
    使用Xshell通过堡垒机登录服务器
    dubbo + zookeeper环境部署
    zookeeper集群部署
    zabbix-3.0.1 添加微信报警
    zabbix-3.0.1结合grafana绘图
    Centos7.2安装zabbix3.0.1简要
  • 原文地址:https://www.cnblogs.com/bad5/p/14633766.html
Copyright © 2011-2022 走看看