C语言学习笔记

面试官:什么是宏定义和内联函数?-腾讯云开发者社区-腾讯云 (tencent.com)

宏定义和内联函数

定义与区别

内联函数:在编译阶段把整个函数体搬过来,空间换时间,减少函数切换栈换来换去的时间消耗。

对于宏定义:宏只是文本替换,不是函数表达,先有宏定义再有内联函数,内联函数延续了宏定义的用法,同时可以避免一些错误。

比如宏定义不加括号得到的结果就有问题:

1、inline函数在第一次被调用前必须进行完整的定义,否则编译器无法知道应该插入什么代码。

2、在inline函数里一般不能含有复杂的控制语句,如for、switch等

3、inline函数是一种用空间换时间的措施,函数体不宜太长,否则反而会增大系统开销,一般为1~5条语句。

4、inline和宏定义相似,但不完全相同,宏定义只做简单的字符替换而不做语法检查,往往会出现意想不到的错误。

宏定义相关:

宏定义define与typedef作用域的的区别:

typedef

如果放在所有函数之外,它的作用域就是从它定义开始直到文件尾

如果放在某个函数内,它的作用域就是从它定义开始直到该函数结尾

#define

不管是在某个函数内,还是在所有函数之外,作用域都是从定义开始直到整个文件结尾(不管是typedef还是define,其作用域都不会扩展到别的文件,即使是同一个程序的不同文件,也不能互相使用)

这里说下题外话#define叫宏定义,但是在笔者的认识里对声明和定义的理解是:声明不分配内存,定义才分配内存,所以#define虽然名字里面有“定义”两个字,但并不占存储空间(为什么不叫宏声明···)

define 和undef(取消宏定义)使用案例

#include <stdio.h>

#define MAX 10
int main()
{
    printf("%d", MAX);
#undef MAX
#define    MAX 20
    printf("%d", MAX);
}

指向数组的指针,访问数组

数组名本身就是指针,表示数组的第一个元素的地址

访问数组,有两种方式,使用指针p或者是直接访问数组。

1. balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。

2. 一旦您把第一个元素的地址存储在 p 中,就可以使用 *p、*(p+1)、*(p+2) 等来访问数组元素。

#include <stdio.h>
 
int main ()
{
   /* 带有 5 个元素的整型数组 */
   double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
   double *p;
   int i;
 
   p = balance;
 
   /* 输出数组中每个元素的值 */
   printf( "使用指针的数组值\n");
   for ( i = 0; i < 5; i++ )
   {
       printf("*(p + %d) : %f\n",  i, *(p + i) );
   }
 
   printf( "使用 balance 作为地址的数组值\n");
   for ( i = 0; i < 5; i++ )
   {
       printf("*(balance + %d) : %f\n",  i, *(balance + i) );
            // %f 格式得到 double,输出是6位小数,%f和%lf是一样的
            // %.10lf 就是输出 10 位小数
   }
 
   return 0;
}

#printfprintf() 只会看到双精度数,printf 的 %f 格式总是得到 double,所以在 printf() 中使用 %f 跟 %lf 的输出显示效果是一样的。但是对于变量来说,double 类型比 float 类型的精度要高。double 精度更高,是指它存储的小数位数更多,但是输出默认都是 6 位小数,如果你想输出更多小数,可以自己控制,比如 %.10lf 就输出 10 位小数。

静态数组和动态数组

区别是内存的大小可不可以变化,

在 C 语言中,有两种类型的数组:

  • 静态数组:编译时分配内存,大小固定。
  • 动态数组:运行时手动分配内存,大小可变。

静态数组的生命周期与作用域相关,而动态数组的生命周期由程序员控制。

在使用动态数组时,需要注意合理地分配和释放内存,以避免内存泄漏和访问无效内存的问题。

malloc动态内存使用

#include <stdio.h>
#include <stdlib.h>

int main() {
    int size = 5;
    int *dynamicArray = (int *)malloc(size * sizeof(int)); // 动态数组内存分配

    if (dynamicArray == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }

    printf("Enter %d elements: ", size);
    for (int i = 0; i < size; i++) {
        scanf("%d", &dynamicArray[i]);
    }
    printf("\n");
    
    printf("Dynamic Array: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", dynamicArray[i]);
    }
    printf("\n");

    free(dynamicArray); // 动态数组内存释放

    return 0;
}

动态数组和静态数组区别:

主要区别就是内存提前不提前分配,

  • 静态数组:编译时分配内存,大小固定。
  • 动态数组:运行时手动分配内存,大小可变。需要手动释放内存

静态内存就是一般的数组

动态内存

  • 内存分配:动态数组的内存空间在运行时通过动态内存分配函数手动分配,并存储在 堆 上。需要使用 malloccalloc 等函数来申请内存,并使用 free 函数来释放内存。
  • 大小可变:动态数组的大小在运行时可以根据需要进行调整。可以使用 realloc 函数来重新分配内存,并改变数组的大小。
  • 生命周期:动态数组的生命周期由程序员控制。需要在使用完数组后手动释放内存,以避免内存泄漏。

关于书上说的“编译的时候分配内存”_c语言编译期间分配内存,为什么编译后的文件大小不变-CSDN博客

要知道代码的本质:其实就是内存的分配,但是为了省事,不能内存都不固定,有些值不需要改就固定了,有些不固定就需要动态。

其实理解本质最好了,因为一个C语言的代码需要进行编译然后再运行:

编译干了什么:编译的时候会把之前的一些固定的(也就是之后不变,不需要用代码来维护)先分好内存地址(放在合适的位置),比如一些全局变量、静态变量等,提前做一些准备工作

运行干了什么:运行就是使用额外的代码维护,在运行期间动态申请的空间,调用这个那个的函数和变量,完成一些逻辑。

编译:就是扫描,进行词法语法检查,代码优化而已,编译程序越好,程序运行的时候越高效。

我想你说的“编译时分配内存”是指“编译时赋初值”,它只是形成一个文本,检查无错误,并没有分配内存空间。 
当你运行时,系统才把程序导入内存。一个进程(即运行中的程序)在主要包括以下五个分区: 栈、堆、bss、data、code。
代码(编译后的二进制代码)放在code区,代码中生成的各种变量、常量按不同类型分别存放在其它四个区。系统依照代码顺序执行,然后依照代码方案改变或调用数据,这就是一个程序的运行过程。

编译时分配内存 --------------- 编译时是不分配内存的。此时只是根据声明时的类型进行占位,到以后程序执行时分配内存才会正确。所以声明是给编译器看的,聪明的编译器能根据声明帮你识别错误。

运行时分配内存 --------------- 这是对的,运行时程序是必须调到“内存”的。因为CPU(其中有多个寄存器)只与内存打交道的。程序在进入实际内存之前要首先分配物理内存。

  • 编译过程 -------------- 只能简单说一下,因为如果要详细的话,就是一本书了《编译原理》。编译器能够识别语法,数据类型等等。然后逐行逐句检查编译成二进制数据的obj文件,然后再由链接程序将其链接成一个EXE文件。此时的程序是以EXE文件的形式存放在磁盘上。
  • 运行过程 -------------- 当执行这个EXE文件以后,此程序就被加载到内存中,成为进程。此时一开始程序会初始化一些全局对象,然后找到入口函数(main()或者WinMain()),就开始按程序的执行语句开始执行。此时需要的内存只能在程序的堆上进行动态增加/释放了。
int main(){
    int a =0; //全局初始化区
    char*p1; //全局未初始化区
int main() {
int b; //栈
char s[] = \"abc\"; //栈
char*p2; //栈
char*p3 = \"123456\"; //123456\\0在常量区,p3在栈上。
staticint c =0;//全局(静态)初始化区
p1 =newchar[10];
p2 =newchar[20];

//分配得来得和字节的区域就在堆区

strcpy(p1, \"123456\"); 

//123456\\0放在常量区,
//编译器可能会将它与p3所指向的\"123456\"优化成一个地方。
}

枚举的定义

默认第一个是0,但是定义了以后,后边就是累加

#include <stdio.h>
 
enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY11
{
      MON11, TUE11=3, WED11, THU11, FRI11, SAT11, SUN11
};//默认第一个是0,这里是MON11=0,
 
int main()
{
    enum DAY day;
    day = WED;
    printf("%d",day);
    
    printf("\n");
    
    enum DAY11 day11;
    day11 = WED11;
    printf("%d",day11);
    return 0;
}

枚举+Switch

#include <stdio.h>
#include <stdlib.h>
int main()
{
 
    enum color { red=1, green, blue };
 
    enum  color favorite_color;
 
    /* 用户输入数字来选择颜色 */
    printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");
    scanf("%u", &favorite_color);
 
    /* 输出结果 */
    switch (favorite_color)
    {
    case red:
        printf("你喜欢的颜色是红色");
        break;
    case green:
        printf("你喜欢的颜色是绿色");
        break;
    case blue:
        printf("你喜欢的颜色是蓝色");
        break;
    default:
        printf("你没有选择你喜欢的颜色");
    }
 
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值