记录有关C语言的12个问题和解答(下)

🧀 I GET IT !

1、数组的容量:数组可以存储的元素个数

数组的容量在声明的时候就要给出,如果不给出容量的话,就必须对数组进行初始化。这个时候数组的容量会自动地根据初始化的元素的个数来确定。假如[]里面是空的,初始化为{1,2,3},那默认这个数组的容量是3。

2、数组的大小:数组占用的内存空间(以字节为单位)

数组大小 = 容量 × 每个元素的大小

3、sizeof() 返回值:数组的大小

计算数组容量可以用公式:
容量 = sizeof(数组名) / sizeof(数组元素类型);

int a[3];
int capacity = sizeof(a) / sizeof(int);  // 容量为3

4、指针运算:自动根据指针类型计算偏移量

  1. a[2] 等价于 *(a + 2)。
  2. 假设数组a的元素都是int型,*(a+2) 的偏移量会自动计算为 2 × sizeof(int) = 8字节
  3. p是一个指针,p+3 表示指针p向后移动3个 对象 。实际的偏移量还是 3 × sizeof(*p);p + 3 的新地址 = p的地址 + 3 × sizeof(*p);

5、字符数组

(1)初始化

在C语言中,字符数组的初始化有一些特殊的规则,允许我使用字符串字面量来初始化字符数组。这是因为字符串字面量在C语言中是以字符数组的形式存储的,并且编译器会自动处理字符串的存储和初始化。(建立临时字符数组,复制给自定义数组)。

char str[6] = "hello";///含 \0,刚好 6 字节
char str[6] = {"hello"};//语法糖,同上

编译器会做以下几件事情:

  1. 分配内存:为 str 分配 10 个字节的内存。
  2. 复制字符串:将字符串 “hello” 的内容(包括结尾的 \0)复制到 str 中。
  3. 填充剩余部分:如果字符串的长度小于数组的大小,剩余的字节会被初始化为 \0。

(2)内容可以修改,但数组名不可直接被赋值

  • 字符数组的内容是可以修改的,因为数组的内存是可写的。
  • 字符数组不能直接赋值,需要使用 strcpy 或类似函数来复制字符串。
str[0] = 'H';//合法
strcpy(str,"abcdog");

(3)字符数组大小固定,内存是静态分配的,在栈上,不需要手动释放。

6、指向字符串常量的字符指针

例子:

char *str = "hello";

存储方面:

  • 字符串常量(如 “hello”)存储在程序的只读内存区域(如 .rodata 段)。
  • 指针本身(如 char *str = “hello”;)通常分配在栈上(如果是局部变量)或全局内存区域(如果是全局变量)。

关于修改:指针可重新赋值,内容不可修改

  • 字符串常量:存储在只读内存中,不能修改。str[0] = 'H'; 是非法操作,会导致未定义行为(通常是程序崩溃,一直不工作~)。
  • 字符指针:可以被直接赋值,指向另一个字符串常量。

大小和长度:

  • 字符指针的大小固定4或8字节;
  • 字符串的长度由字符串常量决定,可以通过 strlen 获取。
char *str = "hello";
int len = strlen(str);  // len = 5

7、动态分配内存的字符指针

例子:

char *str = malloc(10);
strcpy(str, "hello");
int len = strlen(str);  // 实际字符串长度len = 5,\0都不算的
str[0] = 'H';  // 合法操作,将 "hello" 修改为 "Hello"
free(str);  // 必须手动释放内存

存储方面:

  • 使用 malloc、calloc 等函数动态分配内存,内存分配在堆上。
  • 需要手动管理内存,使用 malloc 分配的内存必须显式调用 free 释放。

关于修改:

  • 动态分配的内存是可写的,可以修改str[0] = 'H'; 是合法操作;
  • 不能直接被字符串常量赋值,需要借助 strcpy 或类似函数复制字符串。

大小和长度:

  • 字符指针的大小固定4或8字节;
  • 字符串的长度由分配的内存大小决定,但实际字符串长度可以通过 strlen 获取。

补充:str[0]等价于*(str + 0),无论str是数组名还是字符指针,都可以通过数组下标的方式来访问和修改内存里的内容(前提是没指向内存中只读区域)

虽然7里的 str 是一个指针,但你可以使用数组下标的方式来访问和修改这块内存中的内容。这是因为在C语言中,str[0] 实际上是 *(str + 0) 的语法糖,它们是完全等价的。

语法糖(Syntactic Sugar) 是编程语言中的一个术语,指的是那些为了让代码更易读、更简洁而引入的语法特性。它并不会增加语言的功能,只是提供了一种更“甜”的写法,让程序员写代码时更舒服。

  • arr[i] 是 *(arr + i) 的语法糖。
  • for (int i = 0; i < 10; i++) 是对 while 循环的语法糖。

8、不能取地址的常量表达式

int a = &(10+20); //会被报错

在这里,10+20是一个常量表达式。编译器在编译过程中会把它替换为它自己的值,也就是30。30 是即时值,没有自己的内存地址,因此无法被取地址。

编译器在报错中写道:&作为单目运算符,它的对象只能是左值。

9、用 const 声明的常量可以被取地址,但是,它只能由const声明的指针指向,除非强制类型转换来绕过检查,但是会导致未定义行为

const int x = 10;
const int *p = &x;  // 合法:p 是 const int * 类型
int *q = &x;        // 非法:q 是 int * 类型

因为int * q = &x; 一旦允许,你可能会通过*q改变x的值,这是违反了const的要求的。

10、函数标识符 = 函数名

  • 函数名在表达式中会自动转换为指向该函数的指针。
  • 函数名:标识符,标识函数;
  • 函数指针:变量,存储函数地址;
void foo() {
    printf("Hello, world!\n");
}

int main() {
    void (*func_ptr)() = foo;  // foo 自动转换为函数指针
    func_ptr();  // 调用 foo 函数
    return 0;
}

11、整型和基本整型

  • “整型” 是一个广义的概念,包括 short、int、long、long long、char、unsigned等在内的所有 整数类型
  • int 的中文称呼是“整型” 或 “基本整型”。

12、直接递归是函数体内部调用自己,间接递归通过调用其他调用自己的函数间接调用自己

间接递归的代码:

#include <stdio.h>

// 函数声明
void functionA(int n);
void functionB(int n);

// 间接递归:functionA 调用 functionB,functionB 调用 functionA
void functionA(int n) {
    if (n > 0) {
        printf("A: %d\n", n);
        functionB(n - 1);  // 调用 functionB
    }
}

void functionB(int n) {
    if (n > 0) {
        printf("B: %d\n", n);
        functionA(n - 1);  // 调用 functionA
    }
}

int main() {
    functionA(5);  // 启动间接递归
    return 0;
}

对比两者

  • 直接递归:代码结构简单直观。
  • 间接递归:通过其他函数间接调用自身,代码结构稍复杂,能实现更灵活的递归逻辑。

在这里插入图片描述

  • 函数的栈帧:
  • 创建时间:函数调用时
  • 存储内容:局部变量(一次性给所有{}外的auto分配存储单元,初始化按代码顺序来)、参数、返回地址

13、全局变量的定义与声明

  • 定义:分配内存
    • int global_var = 10;
    • 只能一次;
  • 声明:变量存在哦,变量是xx类型,不要分配存储空间。(类似要吸管、要热饮,不要放年糕~)
    • extern int global_var;
    • 可能有多次声明(但是这是为了支持头文件包含机制);

这里我的问题是:为什么需要多次声明?

如果全局变量定义在文件的末尾,但在前面的代码中需要使用它,就必须通过 extern 提前声明一次。如果全局变量定义在源文件中,在头文件里要用extern声明,其他文件包含头文件。

14、C语言支持两种调用方式

  • 传值调用:数值的副本,无法影响实参。
  • 传地址调用:地址值的副本,需要指针,通过解引用可以改变函数外定义的变量的值。

引用调用是C++允许的。
全局变量:作用域整个文件 → 无需指针or参数传递,所有函数内部可以直接访问和修改全局变量。

🏆 好啦,恭喜我自己走过这段旅程,我们是冠军!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值