本科主要学习的语言就是 C 和 C++,这篇文章把之前对 C/C++ 的基础总结整理出来,应对之后会遇到的很多次面试。

内容可能不太有逻辑性,内容来自之前自己的总结以及 huihut 总结的『[C/C++ 面试基础知识总结』,建议有需求的读者直接移步这位大佬的总结,非常全而且清晰。

const

对于一般的变量来说,需要区分下面这几种写法:

1
2
const int *p;
int const *q;

上面两种写法都是指向const int类型的指针,指针指向的内存不能被修改,但是可以指向另一个内存,如下面这段代码表明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

int main()
{
int b=4;
int c=5;
const int *a= &b;//const 在左边,指针指向的内容为常量 即a的内容为常量
int const *a=&b;//效果如上相同
a=&c;//可以改变a的指向
// *a=9;//错误:但是不能改变a的内容
cout<<*a;
return 0;
}

而下面这种写法表示int类型的const指针,指针所指向的内存可以被修改,但指针不能指向另一个内存。

1
int * const r= &n;

声明一个指向const int类型的const指针:

1
const int * const p=&n;

对于在函数声明中,有如下三种情况:

1
const int& SetPoint(const int& param) const

第一个const指函数的返回值限定为 const,即返回值不能被修改。
第二个const指函数的形参为 const 类型,函数体内不能被修改。
第三个const指这个函数不会对这个类对象的数据成员作任何改变。

static

修饰普通变量

修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值 0 (在静态数据区,内存中所有的字节默认值都是0x00)初始化它。

把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期;把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。static 这个说明符在不同的地方所起的作用是不同的。

例如下面这段代码,定义一个局部的静态变量,尽管多次调用函数,但是静态变量被初始化为 10 后,后面不会再不被初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int test(int j) {
static int i=10;
i=i+j;
return i;
}

int main()
{
int m=test(12);
int n=test(4);
cout << m <<endl; //输出 22
cout << n <<endl; //输出 26
return 0;
}

修饰普通函数

表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命令函数重名,可以将函数定位为 static。

修饰成员变量

1) 如果只声明了类而未定义对象,则类的一般数据成员是不占内存空间的,只有在定义对象时,才为对象的数据成员分配空间。但是静态数据成员不属于某一个对象,在为对象所分配的空间中不包括静态数据成员所占的空间。静态数据成员是在所有对象之外单独开辟空间。只要在类中定义了静态数据成员,即使不定义对象,也为静态数据成员分配空间,它可以被引用。

2) 对于静态变量,如果在一个函数中定义了静态变量,在函数结束时该静态变量并不释放,仍然存在并保留其值。静态数据成员也类似,它不随对象的建立而分配空间,也不随对象的撤销而释放(一般数据成员是在对象建立时分配空间,在对象撤销时释放)。静态数据成员是在程序编译时被分配空间的,到程序结束时才释放空间。
3) 静态数据成员至少需要在类外声明(要不在被引用时会报错),声明同时可以初始化,但只能在类体外进行初始化。

4) 静态成员变量可以通过类和成员函数两种方式进行访问。

5) 有了静态数据成员,各对象之间的数据有了沟通的渠道,实现数据共享,因此可以不使用全局变量。全局变量破坏了封装的原则,不符合面向对象程序的要求。但是也要注意公用静态数据成员与全局变量的不同,静态数据成员的作用域只限于定义该类的作用域内(如果是在一个函数中定义类,那么其中静态数据成员的作用域就是此函数内)。在此作用域内,可以通过类名和域运算符“::”引用静态数据成员,而不论类对象是否存在。

修饰成员函数

当调用一个对象的成员函数(非静态成员函数)时,系统会把该对象的起始地址赋给成员函数的this指针,而静态成员函数并不属于某一对象,它与任何对象都无关,因此静态成员函数没有this指针,既然它没有指向某一对象,就无法对一个对象中的非静态成员进行默认访问(即在引用数据成员时不指定对象名)。

静态成员函数可以直接引用本类中的静态数据成员,因为静态成员同样是属于类的,可以直接引用,静态成员函数中不能有普通成员变量。

在类外调用静态成员函数用 “类名 :: ”作限定词,或通过对象调用。1

inline 内联函数

特征

  • 相当于把内联函数里面的内容写在调用内联函数处;
  • 相当于不用执行进入函数的步骤,直接执行函数体;
  • 相当于宏,却比宏多了类型检查,真正具有函数特性;
  • 不能包含循环、递归、switch 等复杂操作;
  • 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 声明1(加 inline,建议使用)
inline int functionName(int first, int secend,...);

// 声明2(不加 inline)
int functionName(int first, int secend,...);

// 定义
inline int functionName(int first, int secend,...) {/****/};

// 类内定义,隐式内联
class A {
int doA() { return 0; } // 隐式内联
}

// 类外定义,需要显式内联
class A {
int doA();
}
inline int A::doA() { return 0; } // 需要显式内联

编译器对 inline 函数的处理步骤

  1. 将 inline 函数体复制到 inline 函数调用点处;
  1. 为所用 inline 函数中的局部变量分配内存空间;
  1. 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
  1. 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO)。

优缺点

优点

  • 内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。
  • 内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
  • 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
  • 内联函数在运行时可调试,而宏定义不可以。

缺点

  • 代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
  • inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。
  • 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。2