注:本系列博客适合有一定C语言基础的学习者,旨在介绍C++面向对象的编程思想以及其语法实现。
目录
1 宏
1.1 常量宏
1.2 函数宏
1.3 控制宏
1.4 其他
2 class与struct
2.1 区别
2.2 对齐方式
3C++内存分区
本节将主要介绍后续学习过程中所需的基础知识,帮助读者加深对C语言的了解的同时逐步过渡到C++的学习之中。
1 宏
想要理解C语言中宏的作用方式,需要提前大致了解C语言源程序的编译过程:我们通常所说的编译其实细分为四个阶段:预处理、编译、汇编、链接。宏本质上是一种预处理指令,简单来说,其会在编译器发挥作用前,将所有的预处理指令进行简单的文本替换。例如我们熟知的"#include"指令便是指示预处理器打开一个名字为stdio.h的文件,并将它的内容"包含"到当前的程序中;"#define"则是指示预处理器用实际值替换宏定义的字符串。后续内容中我们会还会见到的"#pragma"等,都是重要的预处理指令。
本节中我们会用到的预处理指令如下:
1.1 常量宏
从分类上讲,常量宏属于不含参数的宏的范畴,其可能在日常编程解题中出现不多,但是随着代码量的增大,代码复杂程度增加,其作用会愈发明显。
#define MAX_OP 100000
例如我们可以将100000定义为MAX_OP,在程序中所有与max option(最大操作数)有关的均用MAX_OP代替,这样
- 一方面可以消除“神仙数”,即赋予100000以含义,方便以后阅读程序时,便于理解相关操作的含义。
- 另一方面可以方便修改,当我们的程序在运算大量数据出现错误时,我们通常需要减小数据强度,这时我们仅需要修改宏定义的值,而无需修改每一处100000的值。
1.2 函数宏
#define square(x) ((x)*(x))
例如上图所示,函数宏的写法与函数略有不同,例如其可以没有形参类型,没有返回值类型等,我们在写函数宏时,需要明确把握一个关键点——函数宏只做文本替换,不做函数调用。即程序中所有square(x)出现的地方都会被替换为 ((x)*(x)),那么易知函数宏可以程序的运行速度提高(因为没有函数的调用)。但是除去一些比较经典的函数宏(如swap)外,并不建议大家多写函数宏,因为正是宏的简单文本替换这个特性,可能会导致一些很难发现的错误,例如下图程序的执行结果是11,并非我们预想的25,这是因为程序先进行了第一个add中y和第二个add中x的相乘,并非按照我们想象的先执行两个add之后再相乘。
#include#define add(x,y) x+y int main() { int x = 2, y = 3; int z = add(x,y) * add(x,y); printf("%d", z); return 0; }
以及下图这种情况,就是由于我们在写C程序时想当然的在句末添加了' ; ',但是却忽视了print宏中含有了花括号,而在花括号末尾加分号则表示了语句的结束,故造成了编译报错else没有对应的if。
另外在这里分享几个比较好用的宏函数供大家参考~
#define swap(a, b) { a ^= b; b ^= a; a ^= b; }
//对于a,b为整型时,可以调用该宏进行交换数值。
#define swap(a, b) {
__typeof(a) temp = a;
a = b, b = temp;
}
//这种交换宏比较通用,其除了可以交换内置类型外,还可以交换我们自定义的两个结构体变量的值
//但是我们一般不推荐直接交换结构体对象,我们更倾向于交换结构体指针
//这涉及到了深拷贝与浅拷贝的相关内容,我们会在后续详细讲解
1.3 控制宏
控制宏的写法略有些不同,其没有替换文本,仅仅是定义一个事物,其发挥作用的方式类似于我们日常生活中的button。例如:
#define 'something'
如果我们对C/C++的头文件有所了解的话,我们会发现其中有很多的#ifdef、#ifndef、#endif等语句,例如下图,其含义为:如果我实现定义了#ifdef 后面的这个宏,那我就执行其作用域内的四个操作,并在最后声明结束‘#endif’。头文件中之所以有这么多的条件结构,还有一方面原因是为了防止头文件被多次包含,变量重复定义等出错。
例如我们以后会谈到的C++异常处理中的“断言”语法也涉及到了一个控制宏的使用。当我们定义了NDEBUG以后,程序中所有的断言将不再生效。(具体实现可参考assert.h头文件中的内容)
1.4 其他
C/C++中宏的使用是大部分初学者容易忽视的一部分重要内容,合适的使用宏定义可以使我们的程序更加严谨、有序。受限于博客内容,本文未涉及到可选参数(__VA_ARGS__ 和‘ ... ’),字符串化运算符#等等,有兴趣的读者可以另行了解。
2 class与struct
2.1 区别
在C++中,class与struct非常相似,都属于container类型,其中C++的struct与C语言中的struct并不相同,C++中struct既可以包含成员变量,又可以包含成员函数,而C语言只能包含成员变量。
而在C++中,class于struct也并不完全相同,两者之间有许多细小的差别,这里仅介绍与我们相关度较大的几个:
- 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的;
- class 继承默认是 private 继承,而 struct 继承默认是 public 继承;
- class 可以使用模板,而 struct 不能。
在本系列博客中,我们推荐全部使用class来实现相关操作。
2.2 对齐方式
在C++中,为了提高程序运行效率,程序会对类的成员变量进行一系列的对齐。例如在下图所示代码中,如果利用sizeof输出A和B的大小,会发现A对应24,而B对应16,说明AB中均按照最长的字节对齐,AB中最长均为8,而B中int占据4个,还剩4个足够char储存,剩下double另开8个,共16个。而A中int占四个以后不够double储存,所以会另开8个,同理char也会另开8个,说明int和char虽然不足8个但为了程序运行效率,都占据了8个字节的空间。
对于这种情况,我们可以使用#pragma pack(1)指令来取消对齐,此时类的大小即为成员变量大小之和。(注:此情况不涉及虚函数与虚指针)
#pragma pack(1)
class A{
int a;
double b;
char c;
};
class B{
int a;
char c;
double b;
};
3C++内存分区
在C++程序中,内存大致可以划分为4个区域:代码区、全局区、栈区和堆区。
- 代码区:存放程序的二进制代码,此部分由操作系统进行管理。特点是只读和共享。
- 全局区:全局变量和静态局部变量存放于此处,同时全局区还包括了常量区,字符串常量和其他常量等存放于该区,此区域的数据在程序运行结束后由操作系统自动释放。
- 栈区:存放局部变量的值、函数的形参等,由编译器自动分配释放。其特点是内存连续,速度较快,并且是不由程序员人为控制的。
- 堆区:若不细分讨论自由存储区等概念,我们可以大致认为动态分配的内存都会存储在堆区,包括C语言中的malloc(free),还有C++中的new(delete)分配的内存。此区域的特点是由人为控制,数据不连续,相对来说速度较慢,但生存周期一般来说长于栈区。
新人作者,如有纰漏还请各位大佬指出~



