常量

常量是指在程序运行过程中,其值不能被改变的量
在程序中直接使用常量会出现可读性差,可维护性差的缺点。通常用符号常量解决常量的问题
c中定义符号常量的方法有两种,第一是宏定义,宏定义不能有赋值符,不能以分号结尾,不进行类型检查,只在编译时进行简单的替换,运行时不占内存空间;
第二是在c99标准中加入的const关键字的使用,但这里const定义的是一个只读的变量,并不是真正的常量(打印内存地址和普通变量、全局变量等对比一下就会发现c语言的const变量实际在栈上)
c++中对const进行了扩展,使其可以真正进行符号常量的定义,基本格式为:

const 数据类型 符号常量名=数值;

在声明时必须初始化(即使是全局变量),并且在程序中间不能改变其值(在类的声明中除外,const成员在声明时不需要初始化,但是在实例中必须初始化)
const定义的符号常量在程序运行期间占据内存空间,只是用const来指明该内存空间的只读约束

内联函数

函数涉及到形参空间的分配,实参到形参的数据拷贝,函数入口的转换等等,时间空间的开销相对较高
而宏定义在编译时进行简单的原样替换时开销非常低,并且适用于大多数参数类型从而达到类似函数重载的效果,但比较容易出现各种不可预料的副作用,如:

#define abs(a) (a>0?(a):(-a))      //输出a的绝对值
int m=-2;
cout<<abs(++m);                    //输出结果:0

原因是编译器遇到宏时只是简单的宏替换(++m>0?(++m)😦m)),自增运算至少进行两次而不是一次,因此无法达到输出绝对值的目的
c
中内联函数既具有宏定义的优点,又克服了宏定义的缺点,内联函数定义基本格式为:

inline void 函数名(参数列表)

即在普通函数定义基础上在前面加上inline关键字
编译时在调用func的地方用函数体进行了插入替换(注意我这里说的编译是不包含静态链接过程的),不需要进出系统栈,所以会减少程序执行的调用开销。
由于是函数,所以在运算之前还会进行参数的类型检查,并将参数值传入而不是传入整个表达式,例:

inline abs(int a){
    return (a>0?(a):(-a));
}int main(){
    int a=-2;
    cout<<abs(a);
}

注意,并非所有函数都需要定义为内联函数,一般只会将那些频繁被调用,并且函数体较小的(只有几条语句)函数定义为内联函数。内联函数中一般不应该有循环语句和switch语句,如果有的话执行效率比较低,编译器会自动按照普通函数处理。

inline函数的声明与定义

事实上,inline关键字放在函数声明的地方是没有意义的,inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。关于一个函数是否是inline的,这有点类似于一个变量是否是register类型的,函数实际上到底是不是内联的并不取决于声明的时候是否加上inline,而是要看编译器(把预处理器、链接器等当成编译器一部分)最终决定要怎样对其进行实现,用户其实没有必要、也不应该知道函数是否需要内联。
声明一个函数的定义在于让其作用域内的程序知道有这样一个类型的函数存在,至于其有没有被实现在哪里实现,编译过程中一般是无法得知的,只有到了链接过程中发现找不到编译出的函数代码后才能发现错误。inline函数的内联发生在编译期间,并且内联函数是不会在代码区中持久性存储的,如果inline函数的声明和定义分离,尤其是不在一个编译模块中(比如不在一个文件中),此时编译结束后inline并不能内联到其声明的作用域中,就会发生链接错误。
从代码重复利用的角度讲,内联函数不是真正的函数(甚至更类似于宏),将内联函数作为带参宏的替代方案更为靠谱,而不是真的当做函数使用。在多文件编程时,最好是将内联函数的定义直接放在头文件中,不用inline函数的声明(一是本来就没意义,二是内联函数本来就比较轻量直接定义就行了)。

类的inline成员函数

c++中一个的特性是在类声明体内定义的函数默认都是内联函数。如果在类内定义内联函数也可以增加inline关键字,但编译器会忽略。将inline成员函数定义体放在类外的话则不需要在类内声明时加上inline(没意义),并且必须将定义和声明放在同一个头文件(或者从语法角度来说放在同一个源文件里也没问题)。
存在这个特性可能的原因在于:

  • 首先在c++项目工程中肯定是多文件编程,而且一般是把类的声明放在头文件中,将其实现放在另一个源文件中,这种将声明与实现分离的做法
    • 头文件的意义就是通过预处理器将其包含进其他文件,而预处理器做的就是简单的文本展开(可以理解为一个更大的“宏”)
    • 如果在多个头文件中包含进了多个文件,并且这个头文件里面有函数或类成员的定义,那么展开后就相当于在所有文件中都定义了一遍,链接时这就属于重定向错误
    • 因此在头文件中声明,在另一个源文件中定义,编译后再进行静态链接就可以避免这种问题
  • 类的声明体虽然可以定义函数,但是一般情况下类作为一个oo编程的基本单元是要在多个文件中引入并复用的
    • 在类的定义体内定义成员就意味着该类很可能被其他多个文件包含,如果不定义为内联函数的话就很容易出现以上错误,因此c++标准规定类内定义的成员函数默认为内联函数
  • 此外,编译器在定义或合成默认构造函数时也是自动内联的(这一点个人认为主要是默认构造函数太简单了,本来就很适合内联)

引用

简单地说,引用就是给一个存储单元起的一个别名,即引用与它所引用的变量共享存储单元
引用常见的有三种用法:独立引用、做函数参数、做函数返回值

独立引用

作为函数参数以及作为函数返回类型,独立引用时必须进行初始化,这种情况下别名绑定是永久的,如:

int m=8,&pm=m; pm是m的引用,代表一个存储单元

常见的初始化引用的方式:

int a,&pa=a; 引用一个变量
const float &pb=1.2; 引用一个常量,引用值不能修改
int x=1;const int &px=x; 定义常引用,引用值不能修改,可以起到只给别人可读权限不给可写权限的目的

引用绑定后不可再绑定,这体现了引用的“常量”性质(因此也没有类似于typename&const的写法)
实际上,个人认为引用完全可以理解为一个指针常量的解引用

引用作为函数参数

引用作为函数参数时是c/c函数传地址调用的新的方式,效率很高,使用非常直观
和c一样,c
传值调用时实参和形参是两个不同的单元,结合时实参的值会被拷贝到形参中,这种情况下任何通过改变形参来改变实参的努力都是不会成功的
c为解决改变实参的问题,是通过传递指针的方式来完成,可通过形参指针来间接改变实参
c++的引用可以做到和指针同样的双向传递,并且不需要像指针形参一样额外分配栈空间

传递引用时,形参的名字被看作实参的别名,即形参就是实参本身,实参传递的是实参名而不是地址,此时对形参的操作就相当于改变了实参,例:

void func(int &pnum){
    pnum++;
}
int main(){
    int value=5;
    cout<<func(value);
}   //输出结果:6

引用作为函数返回值

函数返回引用,实际上返回的是一个存储单元(变量),即左值

如果一个函数返回引用,那么函数调用可以出现在赋值号的左边,也就是可以给函数调用赋值,这在c中是做不到的,例:

int &f(int *pint)
{
    return *pint;
}
int main()
{
    int a=10,b;
    b=f(&a)*5;
    f(&a)=88;
    cout<<b<<" "<<a;  //输出:50 88
}

值得注意的是,因为返回的引用是一个存储单元,所以函数返回后这个单元的生命期应该不会结束,否则返回值将没有意义因此与指针一样,返回局部变量的引用是没有意义,并且容易出现未定义行为
另一方面可以从上例就可以发现引用作为左值是有安全隐患的(通过一个函数调用可以修改外部传入参数的值),此时就可以将引用定义为常引用,避免左值绑定内容被修改的问题