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)
    • 静态分配的地址空间是由栈分配的,函数执行结束,该地址空间就会被释放

以上内容均属原创,如有不详或错误,敬请指出。
做别人的宝贝,别来淌我这趟浑水。
原文地址:https://www.cnblogs.com/bad5/p/14633766.html