【01】数组、字符串数组与指针
数组、字符串数组与指针
内存模型
0xFFF...FF | 内核区 |
---|---|
栈空间⬇️ | |
( not used ) | |
堆空间⬆️ | |
数据段 | |
代码段 | |
0x000..00 | ( null ) |
- 栈空间:
- 在函数内定义的变量、数组,都储存在栈空间中;
- 每个函数,定义变量的内存总量有限制,Win10 下 Visual Studio 环境中限制为:1 MB;
- 栈空间是由一个一个栈帧组成的,单个栈帧容量超过限制会导致栈溢出[1](Stack Overflow);
- 申请的栈帧数量太多,会导致第二种栈溢出;
- 每次调用函数时,栈帧压栈;返回的时候,栈帧会弹出;
- 栈帧的管理方式:后进先出;
数组的传递
函数调用
int main(){
int arr[N];
func(arr);
}
- 首先进入主调函数
main()
;main()
调用func()
,申请栈帧;- 数组在函数间传递的时候,会退化成传递首地址;
#include <stdio.h> #define SIZE 5 void print(int arr[]){ //void print(int arr[], int length) printf("print, sizeof(arr) = %d\n", sizeof(arr)); for(int i=0; i<SIZE; ++i){ // i < length printf("%3d", arr[i]); } printf("\n"); } int main(){ int arr[SIZE] = {1, 2, 3, 4, 5}; printf("main, sizeof(arr) = %d\n", sizeof(arr)); printf(arr); //print(arr, sizeof(arr)/sizeof(int)); }
在数组传递的时候,必须补充一个额外的 int 参数,用来表示长度 ( 规范代码见注释 );
二维数组
int arr[2][3];
方括号运算符的结合性是由左到右,即第一个方括号决定了这个数组是几维数组,所以这是一个大小为 2 的 int 型数组,数组的元素是大小为 3 的 int 型数组;从这个角度看,二维数组的本质就是一个一维数组;一维数组的每一个元素都是另一个一维数组,每个元素称为一行,即按行存储;
int arr[2][3] = { {1, 2, 3} , {4, 5, 6} };
//大花括号表示是一个有两个元素的一维数组;
//每个元素又是一个有三个元素的一维数组;
- 二维数组的元素也是连续存储的;
地址运算
arr[i]
的地址:addr + i*sizeof(int)*4
;
arr[i][j]
的地址:addr + i*sizeof(int)*4 + j*sizeof(int)
;
所以二维数组的列大小是有用的,而行大小缺失,不影响元素的访问;
拓展到多维的情况,那就是只有第一个元素没有用,其他的都是有用的;
二维数组的传递
二维数组的本质还是一个一维数组,所以在函数调用的时候,传递二维数组还是会退化为一个地址;
- 与一维地址退化的区别:必须要传递出去,每一列的长度;
#include <stdio.h>
void print(int b[3][4], int length){
//b[3][4]中,行信息没用,可以忽略,而列信息必要
//int b[][4]
printf("sizeof(b) = %d\n", sizeof(b));
for(int i=0; i<length; ++i){
for(int j=0; j<sizeof(b[0])/sizeof(int); ++j){
printf("3d", b[i][j]);
}
}
printf("\n");
}
int main(){
int b[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
print(b, sizeof(b)/sizeof(b[0]));
//length = 数组大小 / 元素大小
}
字符数组
以'\o'
结尾的字符数组被称为字符串,所以字符串要多申请一个空间;
#include <stdio.h>
void myprint(char str[], int length){
for(int i=0; i<length; i++){
if(str[i]=='\0'){
return;
}
printf("%c", str[i]);
}
}
/*
void myprint(char str[]){
for(int i=0; str[i]; ++i){
printf("%c", str[i]);
}
printf("\n");
}
*/
int main(){
char str[] = "Hello,world";
printf("%s\n", str);
myprint(str, sizeof(str)/sizeof(char));
//myprint(str);
return 0;
}
字符串的读取
Scanf控制字符:
- %c :不忽略空白;
- %d,%f,%lf :忽略空白;
如果用 %s
读取字符串的话,在字符串中间包含空格的话,就会停止读取;
⚠️内存越界问题:如果申请的空间小于访问空间,就会越界写入;( scanf() 对长度没有检查)
char str[5] = { 0 }; scanf("%s", str); //输入 10 个字符的话,会写入到数组以外;
用数组的时候,如果不包含长度信息,就不安全;
安全的读取字符串接口
char *fgets( char *str, int num, FILE *stream)
,文件指针用:stdin;
fgets(str, 128, stdin); //一次最多取出 n-1 个字符
myprint(str); //可以包含空格
fgets() 读取的时候会读取输入流中的换行符号\n(oA)
。
为了去除这个换行符号的影响,可以算出其位置,一般是最后一个位置,借助size_t strlen(char *str)
,可以返回到控制符号('\0')之前有多长;
if('\n' == str2[strlen(str2) - 1]){
str2[strlen(str2) - 1] = '\0';
}
字符串赋值函数
char *strcpy(char *to, const char *from)
在遇到 from 的终止符之前,把from中的字符写入到 to 中;由于没有检查数组的长度,因此也是一种不安全的方法;
字符串比较
int strcmp(const char *str1, const char *str2);
比较大小的时候,使用字典序:
return | meaning |
---|---|
< 0 | Str1 < str2 |
= 0 | equal |
> 0 | str1 > str2 |
字符串拼接
char *strcat( char *str1, const char *str2);
是一个不安全的函数,不检查字符串的长度;函数找到第一个字符串的终止符,然后从此开始写入,然后把最后一个位置添加一个终止符,最后返回首地址;
Mem 系列函数
void *memcpy(void *to, const void *from, size_t count);
函数从 from 中复制 count 个字符到 to 中,并返回 to 指针,如果 to 和 from 重叠,则函数行为不确定;
void *memset(void *buffer, int ch, size_t count)
拷贝 ch 到 buffer 从头开始的 count 个字符中,并返回 buffer 指针,在运行的时候,int ch
会进行一次类型转换;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了