【01】数组、字符串数组与指针

数组、字符串数组与指针

内存模型

0xFFF...FF 内核区
栈空间⬇️
( not used )
堆空间⬆️
数据段
代码段
0x000..00 ( null )
  • 栈空间:
    • 在函数内定义的变量、数组,都储存在栈空间中;
    • 每个函数,定义变量的内存总量有限制,Win10 下 Visual Studio 环境中限制为:1 MB;
    • 栈空间是由一个一个栈帧组成的,单个栈帧容量超过限制会导致栈溢出[1](Stack Overflow);
    • 申请的栈帧数量太多,会导致第二种栈溢出;
    • 每次调用函数时,栈帧压栈;返回的时候,栈帧会弹出;
  • 栈帧的管理方式:后进先出;

数组的传递

函数调用

int main(){
   int arr[N];
   func(arr);
}
  1. 首先进入主调函数main();
  2. main()调用func(),申请栈帧;
  3. 数组在函数间传递的时候,会退化成传递首地址
#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));
}

image-20210720213036633

在数组传递的时候,必须补充一个额外的 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 会进行一次类型转换;

posted @ 2021-07-23 12:59  CharmingZh  阅读(63)  评论(0编辑  收藏  举报