C语言_数组、指针和函数

本文详细介绍了C语言中的数组内存存储、指针概念及操作,包括指针与整数相加减、指针递增、指针求差等,并探讨了函数、全局变量和局部变量、堆栈空间差异。同时,通过实例解析了数组、指针在函数中的应用,强调了基础知识的重要性。
摘要由CSDN通过智能技术生成

数组、指针和函数

0、前言

之前大一的时候,学习C语言,学到数组的时候感觉还行,可以接受。但是学到指针和函数后,绕来绕去的,就开始懵圈了。

但是,指针的重要性不言而喻,检验C语言学得好不好,就看“指针和内存管理”了。

大一下之后呢,就一直学Java,虽说原理都是相近的,但是毕竟C的语法还是比较细的,而且最近因为一些原因呢,要用C语言实现算法,所以抽空补补了C的基础和语法,特此记录一下。

1、数组的内存中的存储

那些简单的数组定义、初始化等比较简单就不说明,我们重点来看数组在内存中的存储。

在此,我们先来补充一下内存地址原理,其实这个知识点是《计算机组成原理》的内容,在此简单提一下,顺带补充一句,基础很重要,小伙伴们要重视基础哦!

假设我们的机子32位,则会使用4字节来存储地址,例如”0x11223344”;若为64位,则用8字节来存储地址,例如“0x1122334455667788”。

以32位机为例:

在这里插入图片描述

接着,我们先写一段很简单的程序,然后我们再来看,VS2022调试中的内存映像。

#include<stdio.h>

int main() {
	int arr[5] = { 11,22,33,44,55 };
	for (int i = 0; i < 5; i++) {
		printf("%d\n", arr[i]);
	}

	return 0;
}

内存映像:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

通过内存映像我们可以很清楚的看到,例如:

  • 一个int类型确实占4字节
  • 数组在内存中是顺序存放的,大小根据元素的类型和个数决定
  • 数组名就是数组的首地址,也就是数组第一个元素的地址

2、指针

2.1、指针和指针变量

指针:一个变量的地址称为该变量的指针,也就是说,指针就是变量的地址。

指针变量:用来存放为“地址”的变量。

int num = 18;
int *point = &num;

假设32位机,num存放的地址为“0x 11 22 33 44”。

我们就说,变量num的地址(指针)是“0x 11 22 33 44”;指针变量point的值是“0x 11 22 33 44”。

我们都知道int类型的变量num占4个字节(32位机),那么指针变量point占多大的空间呢?答若为32位机,寻址范围为32位即4字节;若为64位机,则寻址范围为64位即8字节。

在这里插入图片描述

在这里插入图片描述


2.2、操作符

  1. 取地址操作符为“&”,也称“引用”,通过该操作符我们可以获取一个变量的地址值;

  2. 取值操作符为“*”,也称“解引用”,通过该操作符我们可以得到一个地址对应的数据。

#include<stdio.h>

int main() {

	int num = 15;
	int* point = &num;

	printf("num: %d\n", num);
    //若编译器不支持%p,可以用%u、%lu来代替
	printf("&num: %p\n",&num);
	printf("point: %p\n",point);
	printf("*point: %d\n",*point);

	return 0;
}
/*
结果:
num: 15
&num: 00EFFCBC
point: 00EFFCBC
*point: 15
*/

在这里插入图片描述


2.3、指针操作

2.3.1、指针和整数相加减

我们以加法为例,减法同理。

#include<stdio.h>

int main() {
	int arr[5] = { 1,2,3,4,5 };
	int* point = arr;
    
	printf("%p\n", point);
	printf("%p\n", point+2);
	printf("%d\n", *point);
	printf("%d\n", *(point+2));
	
	return 0;
}
/*
结果:
00DDFD00
00DDFD08
1
3
*/

在这里插入图片描述

0x 00 DD FD 00 -- 0x 00 DD FD 08,一共是相差8个字节,即两个int类型变量所占的空间

用"+"运算符把指针和整数相加(相减),整数都会和指针所指向类型的大小相乘(以字节为单位),再将结果与初始地址相加。
即,0x00DDFD00 + 2 * 4Byte = 00DDFD08

point + 2后的值就是arr[2]的地址,即(point + 2)与&arr[2]等价
2.3.2、指针递增
#include<stdio.h>

int main() {
	int arr[5] = { 1,2,3,4,5 };
	int* point = &arr[2];

	printf("%p\n", point);
	printf("%d\n", *point);
	printf("%p\n", ++point);
	printf("%d\n", *point);
	
	return 0;
}
/*
结果:
00F8FE78
3
00F8FE7C
4
*/

在这里插入图片描述

0x 00 F8 FE 78 --- 0x 00 F8 FE 7C,一共相差4个字节

一开始point指向arr[2],其值为"00F8FE78";而后point递增后指向arr[3],其值为"00F8FE7C"

所以,point++相当于把point的值加上4,即 加上(1 * 4 Byte)
2.3.3、指针求差
#include<stdio.h>

int main() {
	int arr[5] = { 1,2,3,4,5 };
	int* point01 = &arr[2];
	int* point02 = &arr[5];

	printf("%p\n", point01);
	printf("%p\n", point02);
	printf("%d\n", point02 - point01);

	return 0;
}
/*
结果:
006FFB38
006FFB44
3
*/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eDuYawj3-1651588023408)(E:\Blog\C语言\数组、指针和函数.assets\image-20220430223535900.png)]

0x 00 6F FB 38 --- 0x 00 6F FB 44,一共相差12个字节

point02 - point01 得3,意思是两个指针所指向得两个元素之间相差3个int,即12个字节
2.3.4、& 和 * 同时使用
//若有此语句
int a = 18;
int* point = &a;

&*point 和 *&a 的含义和区别

“&”和“*”两个运算符的优先级别相同,但要按自右向左的方向结合。

因此,&* point 与 &a 相同,都表示变量a的地址,也就是point。

而*&a,首先进行&a运算,得到a的地址,再进行*运算。*&a和*point的作用是一样的,它们都等价于变量a,即*&a 与a等价。


2.4、指针类型与地址

地址应该和指针类型兼容,也就是说,我们不能把int型变量的地址赋值给指向double类型的指针,只能将int型变量的地址赋值给指向int类型的指针变量中。

用代码表示如下:

int num01 = 18;
double num02 = 3.14;
int* point01 = &num01;		//正确,歪瑞good
double* point02 = &num01; 	//毫无意义而且会出错
double* poinr03 = &num02;	//正确,歪瑞good

我们在开发过程中,应该避免出现这种问题。

但是,如果硬要这么做的话,又有什么影响呢?

其实是可以赋值的,但是会出现数据截断等错误,而且不能进行指针操作,由于篇幅所限,详情请看《C语言_地址与指针类型不兼容造成的影响》


3、函数

3.1、全局变量和局部变量

#include<stdio.h>

int i = 18;	// 全局变量

void print(int a) {
	printf("print -- &a = %p\n", &a);
	printf("print -- a = %d\n", a);
}

int main() {
	printf("main -- &i = %p\n", &i);
	printf("main -- i = %d\n", i);

	print(i);

	int i = 5;	//局部变量
	printf("main -- &i = %p\n", &i);
	printf("main -- i = %d\n", i);

	print(i);
	return 0;
}

在这里插入图片描述

全局变量i存储在数据段,所以main函数和my_print函数都是可见的。

全局变量不会因为某个函数执行结束而消失,在整个进程的执行过程中始终有效,因此开发中应尽量避免使用全局变量!

我们在函数内定义的变量都称为局部变量,局部变量存储在自己的函数对应的栈空间内,函数执行结束后,函数内的局部变量所分配的空间将会得到释放。

如果局部变量与全局变量重名,那么将采取就近原则,即实际获取和修改的值是局部变量的值。

在这里插入图片描述


3.2、堆空间和栈空间的差异

我们用一个例子来看一看栈空间和堆空间的差异。

#include <stdio.h>

char* print_stack()
{
	char c[17] = "I am print_stack";
	puts(c);//能正常打印
	return c;
}

char* print_malloc()
{
    //malloc是在堆空间中开辟一块内存
	char* p = (char*)malloc(30);
	strcpy(p, "I am print_malloc");
	puts(p);
	return p;
}

int main()
{
	char* p;
	p = print_stack();//栈空间会随着函数的执行结束而释放,相当于p=c
	puts(p);//打印不出来
	p = print_malloc();//堆空间不会随子函数的结束而释放,必须自己free
	puts(p);
	free(p);
	return 0;
}

在这里插入图片描述


4、函数、数组和指针

假设我们有一个需求,是要对数组进行求和,我们可以怎样定义函数呢?

#include<stdio.h>
#define NUM 10

int getSum01(int arr[], int num);

int getSum02(int* arr, int num);

int getSum03(int* start, int* end);

int main() {
    int arr[NUM] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int* point = arr;
    int result01 = getSum01(arr, NUM);
    int result02 = getSum02(point, NUM);
    int result03 = getSum03(point, point + NUM);
    printf("result01 = %d\n", result01);
    printf("result02 = %d\n", result02);
    printf("result03 = %d\n", result03);
    return 0;
}

int getSum01(int arr[], int num) {
    printf("getSum01:arr = %p\n", arr);
    int result = 0;
    for (int i = 0; i < num; i++) {
        result += arr[i];
    }
    return result;
}

int getSum02(int* arr, int num) {
    printf("getSum02:arr = %p\n", arr);
    int result = 0;
    for (int i = 0; i < num; i++) {
        result += *(arr + i);
    }
    return result;
}

int getSum03(int* start, int* end) {
    printf("getSum03:start = %p\n", start);
    printf("getSum03:end = %p\n", end);
    int result = 0;
    while (start < end) {
        result += *start;
        /*
         * 让指针指向下一个元素
         * 指针++并不是指向下一个字节的地址;
         * 而是指向下一个元素的地址,增量依据类型而定
         */
        start++;
    }
    return result;
}


/*
结果:
getSum01:arr = 006FFB30
getSum02:arr = 006FFB30
getSum03:start = 006FFB30
getSum03:end = 006FFB58
result01 = 55
result02 = 55
result03 = 55
*/

在这里插入图片描述

int getSum01(int arr[], int num);
int getSum02(int* arr, int num);
int getSum03(int* start, int* end);

从输出的结果可以出,以上三个函数是等价的!

之前,我们也说过,数组名就是数组首元素地址。

无论是将函数形参定义成“数组”亦或是“指针”,我们传入的“数组名”亦或是“指针”,其实在底层最后传的都是“指针”,也就是“地址”,对应到本例就是“数组首元素的地址”。

这样也就能明白,为什么我们定义函数时需要两个形式参数,即“数组地址”和“数组大小”。

因为在函数实现中,我们并不知道数组的长度,盲目运算可能会造成空指针异常,因此我们要手动地传入“数组长度”。当然,我们也可以传入“数组首地址”和“数组尾地址”,其实原理都是一样的。


emmm,大概就唠这么多吧,最近发现基础真的重要!!!

注:如有错误,敬请指正!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

窝在角落里学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值