IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言_数组、指针和函数 -> 正文阅读

[C++知识库]C语言_数组、指针和函数

数组、指针和函数

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,大概就唠这么多吧,最近发现基础真的重要!!!

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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 10:59:30  更:2022-05-05 11:00:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:08:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码