注意: 本内容为摘抄网上的学习资料,作为个人笔记使用,如有侵权, 立刻删除。
(1)宏的应用
- 宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对 “参数” 进行的是一对一的替换。
assert() 断言
断言,是宏,而非函数。assert 宏的原型定义在 <assert.h>
(C)、<cassert>
(C++)中,其作用是如果它的条件返回错误,则终止程序执行。可以通过定义 NDEBUG
来关闭 assert,但是需要在源代码的开头,include <assert.h>
之前。
assert() 使用
#define NDEBUG // 加上这行,则 assert 不可用
#include <assert.h>
assert( p != NULL ); // assert 不可用
宏的应用
用#define声明一个常数,表明1年中有多少秒(忽略闰年问题)?
答案:#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
要点:
A.没有以分号结束;
B.懂得预处理器将为你计算常数表达式的值。即60 * 60 * 24 * 365而不是31536000.
C.考虑到了16位机将会溢出,巧妙运用了UL。
//U和L是 整数文字量的后缀修饰,用于显示指明整数文字量的类型为unsigned int(U)和long int(L)
写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个?
答案:#define MIN(A,B) ((A) <= (B) ? (A) : (B))
要点:
A.参数用括号括起来;
B.考察能否合理运用条件运算符;
宏实现比较大小,以及两个数中的最小值
面试高频指数:★☆☆☆☆
具体代码如下:
#include <iostream>
#define MAX(X, Y) ((X)>(Y)?(X):(Y))
#define MIN(X, Y) ((X)<(Y)?(X):(Y))
using namespace std;
int main ()
{
int var1 = 10, var2 = 100;
cout << MAX(var1, var2) << endl;
cout << MIN(var1, var2) << endl;
return 0;
}
/*
程序运行结果:
100
10
*/
请你来回答一下++ i和i++ 的区别
++ i先自增1,再返回,i ++先返回i,再自增1
请你来说一说 ++i和i ++的实现
- ++ i 实现:
int& int::operator++()
{
*this +=1;
return *this;
}
*this指针、面向对象引用 int& int::operator++()、都不懂
- i ++ 实现:
const int int::operator(int)
{
int oldValue = *this;
++(*this);
return oldValue;
}
i ++是否原子操作?并解释为什么?
i++的操作分三步:
- 栈中取出i
- i自增1
- 将i存到栈
所以 i++不是原子操作,上面的三个步骤中任何一个步骤同时操作,都可能导致i的值不正确自增。
:: 范围解析运算符
分类
- 全局作用域符(
::name
):用于类型名称(类、类成员、成员函数、变量等)前,表示作用域为全局命名空间 - 类作用域符(
class::name
):用于表示指定类型的作用域范围是具体某个类的 - 命名空间作用域符(
namespace::name
):用于表示指定类型的作用域范围是具体某个命名空间的
:: 使用
int count = 11; // 全局(::)的 count
class A {
public:
static int count; // 类 A 的 count(A::count)
};
int A::count = 21;
void fun()
{
int count = 31; // 初始化局部的 count 为 31
count = 32; // 设置局部的 count 的值为 32
}
int main() {
::count = 12; // 测试 1:设置全局的 count 的值为 12
A::count = 22; // 测试 2:设置类 A 的 count 为 22
fun(); // 测试 3
return 0;
}
C++语言特性性能分析
屁话
通常大多数开发人员认为,汇编语言和C语言比较适合编写对性能要求非常高的程序,
C++语言主要适用于编写复杂度非常高但性能要求并不是很高的程序。因为大多数开发人员认为,C++语言设计时因为考虑到支持多种编程模式(如面向对象编程和范型编程)以及异常处理等,从而引入了太多新的语言特性。新的语言特性往往使得C++编译器在编译程序时插入了很多额外的代码,会导致最终生成的二进制代码体积膨胀,而且执行速度下降。
但事实并非如此,通常一个程序的速度在框架设计完成时大致已经确定,而并非因为采用C++语言才导致速度没有达到预期目标。因此,当一个程序的性能需要提高时,首先需要做的是用性能检测工具对其运行的时间分布进行一个准确的测量,找出关键路径和真正的性能瓶颈所在,然后针对性能瓶颈进行分析和优化,而不是主观地将性能问题归咎于程序所采用的语言。工程实践表明,如果框架设计不做修改,即使使用C语言或汇编语言重新改写,也并不能保证提高总体性能。
因此,遇到性能问题时,首先应检查和反思程序的总体架构,然后使用性能检测工具对其实际运行做准确的测量,再针对性能瓶颈进行分析和优化。
重点
但C++语言中确实有一些操作、特性比其它因素更容易成为程序的性能瓶颈,常见因素如下:
(1)缺页
缺页通常意味着要访问外部存储,因为外部存储访问相对于访问内存或代码执行,有数量级的差别。因此,只要有可能,应该尽量想办法减少缺页。
(2)从堆中动态申请和释放内存
C语言中的malloc/free和C++语言中的new/delete操作时非常耗时的,因此要尽可能优先考虑从线程栈中获取内存。优先考虑栈而减少从动态堆中申请内存,不仅因为在堆中分配内存比在栈中要慢很多,而且还与尽量减少缺页有关。当程序执行时,当前栈帧空间所在的内存页肯定在物理内存中,因此程序代码对其中变量的存取不会引起缺页;如果从堆空间生成对象,只有指向对象的指针在栈上,对象本身则存储在堆空间中。堆一般不可能都在物理内存中,而且由于堆分配内存的特性,即使两个相邻生成的对象,也很有可能在堆内存位置上相距很远。因此,当访问两个对象时,虽然分别指向两个对象的指针都在栈上,但通过两个指针引用对象时很有可能会引起两次缺页。
(3)复杂对象的创建和销毁
复杂对象的创建和销毁会比较耗时,因此对于层次较深的递归调用需要重点关注递归内部的对象创建。其次,编译器生成的临时对象因为在程序的源码中看不到,更不容易察觉,因此需要重点关注。
(4)函数调用
由于函数调用有固定的额外开销,因此当函数体的代码量相对较少,并且函数被非常频繁调用时,函数调用时的固定开销容易成为不必要的开销。C语言的宏和C++的内联函数都是为了在保持函数调用的模块化特征基础上消除函数调用的固定额外开销而引入的。由于C语言的宏在性能优势的同时也给开发和调试带来不便,因此C++语言中推荐使用内联函数。
(2)模板
模板函数和模板特化
引入原因
编写单一的模板,它能适应多种类型的需求,使每种类型都具有相同的功能,但对于某种特定类型,如果要实现其特有的功能,单一模板就无法做到,这时就需要模板特例化
定义
对单一模板提供的一个特殊实例,它将一个或多个模板参数绑定到特定的类型或值上
(1)模板函数特例化
必须为原函数模板的每个模板参数都提供实参,且使用关键字template后跟一个空尖括号对<>,表明将原模板的所有模板参数提供实参,举例如下:
template<typename T> //模板函数
int compare(const T &v1,const T &v2)
{
if(v1 > v2) return -1;
if(v2 > v1) return 1;
return 0;
}
//模板特例化,满足针对字符串特定的比较,要提供所有实参,这里只有一个T
template<>
int compare(const char* const &v1,const char* const &v2)
{
return strcmp(p1,p2);
}
本质
特例化的本质是实例化一个模板,而非重载它。特例化不影响参数匹配。参数匹配都以最佳匹配为原则。例如,此处如果是compare(3,5),则调用普通的模板,若为compare(“hi”,”haha”)则调用特例化版本(因为这个cosnt char*相对于T,更匹配实参类型),注意二者函数体的语句不一样了,实现不同功能。
注意
模板及其特例化版本应该声明在同一个头文件中,且所有同名模板的声明应该放在前面,后面放特例化版本。
(2)类模板特例化
原理类似函数模板,不过在类中,我们可以对模板进行特例化,也可以对类进行部分特例化。对类进行特例化时,仍然用template<>表示是一个特例化版本,例如:
template<>
class hash<sales_data>
{
size_t operator()(sales_data& s);
//里面所有T都换成特例化类型版本sales_data
//按照最佳匹配原则,若T != sales_data,就用普通类模板,否则,就使用含有特定功能的特例化版本。
};
类模板的部分特例化
不必为所有模板参数提供实参,可以指定一部分而非所有模板参数,一个类模板的部分特例化本身仍是一个模板,使用它时还必须为其特例化版本中未指定的模板参数提供实参(特例化时类名一定要和原来的模板相同,只是参数类型不同,按最佳匹配原则,哪个最匹配,就用相应的模板)
特例化类中的部分成员
可以特例化类中的部分成员函数而不是整个类,举个例子:
没读明白,可能以后用到再读能清楚点吧
template<typename T>
class Foo
{
void Bar();
void Barst(T a)();
};
template<>
void Foo<int>::Bar()
{
//进行int类型的特例化处理
cout << "我是int型特例化" << endl;
}
Foo<string> fs;
Foo<int> fi;//使用特例化
fs.Bar();//使用的是普通模板,即Foo<string>::Bar()
fi.Bar();//特例化版本,执行Foo<int>::Bar()
//Foo<string>::Bar()和Foo<int>::Bar()功能不同