C 语言回顾

  1. 当你写下面代码发生了什么
 least = MIN(*p++, b);
 
 结果是:((p++) <= (b)? (p++): (*p++)) 这个表达式会产生副作用,指针 p 会3次++自增操作。
  1. 用预处理指令#define 声明一个常数,用表示1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (365 * 24 * 60 * 60)UL (UL 表示无符号整形)
  1. 写一个标准的宏MIN
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
  1. define 和 const 区别

#define 定义宏的指令,
    是程序在预处理阶段将所有#define 定义的内容进行替换。
    因此程序在运行时,常亮中没有用#define 所定义的宏,系统不为它分配内存,
    而且在编译阶段不会检查数据类型,出错率大一些。
    #define 定义的表达式要注意边缘效应
    
    #define N 2 + 3
    int a = N / 2; // a 的值3.5,

const 定义的常量,在程序运行时是存放在常量列表中,系统会为它分配内存,而且在编译时进行类型检查。
  1. 关键字 volatile 有什么含义?
  • 优化器在用到变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器的备份。
  • 并行设备的硬件寄存器(如:状态寄存器)
  • 一个中断服务子程序中会访问到的非自动变量(non-automatic veriables)
  • 多线程应用中被几个任务共享的变量
  1. 字符串拷贝 sprintf、strcpy、memcpy,请问些函数有什么区别
  • 这些函数的区别在于实现的功能以及操作对象不同。
  • strcpy:函数操作的对象是字符串,完成从源字符串到目的字符串的拷贝功能
  • sprintf:这个函数主要用来实现(字符串或者基本数据类型)向字符串转换的功能。如果源对象是字符串,并且指定%s 格式符,也可实现字符串拷贝功能。
  • memcpy:函数顾名思义就是内存拷贝,实现讲一个内存的内容复制到另一个内存块这一功能。内存块由其首地址以及长度确定。因此,memcpy的操作对象适用于任意数据类型,只要能给出对象的起始地址和内存长度信息、并且对象具有可操作性即可。鉴于 memcpy 函数等长拷贝的特点以及数据类型代表的物理意义,memcpy 函数通常限于同种数据或者对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型拷贝。
  • 对于字符串拷贝来说,用上述3个函数都可以实现,但是其实现的效率和使用方便程度不同
  • strcpy 无疑是最合适的选择:效率高且调用方便。
  • sprintf 要额外指定格式符并且进行格式化,麻烦而且效率不高
  • memcpy 虽然高效,但是 需要额外提过拷贝的内存长度这一参数,易出错使用不便,并且如果长度指定过大的话(最优长度的源字符串长度+1),这样带来性能的下降。其实strcpy 函数一般是在内部使用 memcpy 函数或者用汇编直接实现的,已达到高效的目的,因此,使用 memcpy 和 strcpy 拷贝字符串在性能上应该没有什么大的区别。
  • 对于字符串类型的数据复制来说,strcpy 和 sprintf一般就无能为力了,可是对 memcpy 却没有什么影响。但是,对于基本数据类型来说,尽管可以使用 memcpy 进行拷贝,由于有赋值运算符可以方便且高效地进行同种或兼容类型的数据之间的拷贝,所以这种情况下 memcpy 几乎不被使用。memcpy 的长处是用来实现(通常是内部实现居多)对结构或者数组的拷贝,其目的是或者高效,或者使用方便,甚或两者兼有。
  1. static 关键字的作用
  • 隐藏。编译多个文件时,所有未加 static 前缀的全局变量和函数都全局可见。
  • 保持变量内容的持久。全局变量和 static 变量都存储在静态存储区,程序开始运行就初始化,只初始化一次。static 控制了变量的作用范围。
  • 默认初始化为0,在静态数据区,内存中的所有字节都是0x00,全局变量和 static 变量都是默认初始化为0.
  • static 全局变量只被初始化一次,防止在其他文件单元中被引用
  • static 局部变量只被初始化一次,下次进来还是上一次的值
  • static 函数在内存中只有一份,普通函数在每个被调用中位置一份
  1. 关键字 const
  • const int a; int const a; 作用是一样的:a 是一个常整型数
  • int * const a; a 是一个执行整型的常指针(指针指向的整型数是可以修改的,但是指针是不可以修改的)
  • int const * const a; a 是一个指向常整形术的常指针(指针指向的整型数是不可以修改的,同时指针也是不可以修改的)
  1. 堆栈
  • 管理方式:对于栈来说,是有编译器自动管理的,无需我们手工控制;对于堆来说,释放工作有程序员来控制的,容易产生内存泄漏(memory leak)。
  • 申请大小:
    • 栈:在 Windows 下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在Windows 下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小。
    • 堆:堆是想高地址宽展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址到高地址。对的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
  • 碎片问题: 对于堆来讲,频繁的 new、delete 势必会早场内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来说,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出。
  • 分配方式:对都是动态分配的,没有静态的堆。栈有2中分配方式:静态分配和动态分配。静态分配是由编译器完成的,比如局部变量的分配。动态分配是有 alloc 函数进行分配的,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
  • 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是 C/C++函数库提供的,它的机制是很复杂的。
  1. 数组和指针的区别
  • 数组可以申请在栈区和数据区;指针可以指向任意类型的内存快;sizeof 作用于数组时,得到的是数组所占的内存大小;作用于指针时,得到的是4个字节的大小
  • 数组名表示数组首地址,是常亮指针,不可以修改指向。比如不可以将++作用于数组名上;普通指针的值可以改变,比如可将++作用于指针上。
  • 用字符串初始化字符数组是将字符串的内容拷贝到字符数组中;用字符串初始化字符指针是将字符串的首地址赋值给指针,也就是指针指向了该字符串。
  1. 引用和指针的区别
  • 指针是指向一块内存,内容存储所指向的内存的地址。
  • 引用是某块内存的别名。
  • 引用使用时不需要解引用(*)而指针需要
  • 引用只在定义时被初始化,之后不可改变,指针可变。
  • 引用没有 const
  • 引用不能为空
  • sizeof 引用得到的是指向变量(对象)的大小,sizeof 指针是指针本身的大小。
  • 指针和引用的自增(++)运算意义不一样:引用++为引用对象自己++,指针++是指向对象后面的内存
  • 程序需要为指针分配内存区域,引用不需要
  1. 用变量 a 给出下面定义
- 一个整数
- 一个指向整型类型的指针
- 一个指向指针的指针,它指向的指针是指向一个整型数
- 一个有10个整数型的数组
- 一个有10个指针的数组,该指针是指向一个整数型的。
- 一个指向有10个整型数数组指针。
- 一个指向函数的指针,该函数有一个整型参数并返回一个整型数。
- 一个有10个指针的数组,该指针指向一个函数,该函数有一个整形参数并返回一个整型数。

* int a;
* int *a;
* int **a;
* int a[10];
* int *a[10];
* int (*a)[10];
* int (*a) (int);
* int (*a[10])(int);
  1. 内存分区情况
  • 代码区:存放函数二进制代码
  • 数据区:系统运行时申请内存并初始化,系统退出时由系统释放,存放全局变量、静态变量、常亮
  • 堆区:通过 malloc 等函数或者 new 等操作动态申请得到,需要程序员手动申请和释放
  • 栈区:函数模块内申请,函数结束时由系统自动释放,存放局部变量、函数参数

你可能感兴趣的:(C 语言回顾)