Learn:C++ Primer Plus Chapter 8

前言

这是一个学习笔记,which 是我读的《C++ Primer Plus》。我想尽可能的将感觉有用的内容记录下来供以后来进行查阅。Whose前7章都是一些C语言的相关内容,大致扫过了一下,感觉没有什么 Value 就从第8章 startnote。希望自己能够将这本书看完。

第8章函数探幽

1. inline函数

通过在函数声明的前方添加 inline 可以使得编译器生成内联的函数。函数内联是一种编译器的优化方式,对于函数体积较小的函数编译器一般会优化进而节约使用 call 指令进行调用的开销。体积大的函数即使 添加了 inline 关键字也不一定会被内联。

inline void func()	//inline函数

我在此处记录的原因是 want(向,想,像这几个字太恶心了) 记录下一个有关(macro)展开的应用。下面充分说明宏展开的过程和函数调用的区别,which 只会进行替换而不考虑具体语义。

#define SQUARE(x) x * x
a = SQUARE(5.0);	//is replaced by a = 5.0 * 5.0
b = SQUARE(4.5 + 7.5); 	//is replaced by b = 4.5 + 7.5 * 4.5 + 7.5
c = SQUARE(c++);	//is replaced by c = c++ * c++

2. 引用变量(& 和 &&)

引用变量的一般特性就不进行赘述了,一般人都是了解的。这里我记录下我看书时学到的东西。

  1. & LValue Reference 符号进行左值引用,右边必须具有实体。
  2. && RValue Reference 符号进行右值引用,右边不必具有实体。
  3. * Dereference 符号进行解引用 。

左值引用就是我们一般理解的引用,而右值引用则是在 C++11 中新引入的一种方式,下面的列子很好的解释右值引用的作用,即实现移动语义(move semantics)。即 “=” 右边的数值不需要有具体的存储空间,会自动生成一个临时的空间来存放。

我个人理解这个实现的过程是:编译器现在栈中开辟一块空间,接着引用这个空间的内容,隐式的(implicit)分配空间。

//lvalue reference
double a = 1.0;
double &aref = a;

//example of && rvalue reference
double && rref = 6.0;	//not allowed for double &
double & j = 15.0;
double && jref = 2.0 * j + 18.5;	//not allowed for double &
cout << rref << endl;	//display 6.0
cout << jref << endl;	//display 48.5

另一个值得一说的是函数传递参数时的引用,当形参有 const 保留字进行修饰时一下情景(scenario)会生成临时参数在函数的栈中:

  1. 实参的类型正确,但不是左值(即没有具体存储空间)。
  2. 实参类型不正确,但可以转化为正确类型。
//there is a function possessing a referring argument
void func(const int &a);

func(1);	//the fisrt scenrio, which don't have the left value. the argument is a instant number

double b;
func(b);	//the second scenrio, which can transfer to the int

3. 函数

3.1 默认参数

C++ 为函数的使用引入的新的内容——默认参数。默认参数在实际进行程序设计时会减少程序员的工作量,in a way that 减少析构函数(destruct function)和方法(method)以及方法重载的数量。

默认参数遵守这样的规则:默认参数必须从右到左添加默认值。In other word,要为某个参数添加默认值则它右边的所有参数都必须有默认值。In my opinion,这样规定是为了区分什么时候使用默认值,也就是确定哪些参数没有传递实参。

// below is examples
// wrong default argument, because the right args don't have the default arg, 
//this will cause the compiler can't make sure the argument belong to which arg
int func1(int a, int b=2, int c, int d);

// correct default argument
int func2(int a, int b=2, int c=3, int d=4);
func2(1);	//same as func2(1, 2, 3, 4)
func2(1,312);	//same as func2(1, 312, 3, 4)

3.2 函数重载

函数重载多态两个术语指的是同一件事,即一个函数可以有多种形态,函数使用时最关键的就是函数名,因此就是多个同名函数。In 书中对这两个术语都进行了解释, 我将其融合,而最后书中选择了使用函数重载来进行使用。

函数重载的实现依赖于名称粉碎(mangling name),也叫名称修饰(decorated name)。编译器生成的二进制代码中是不允许出现同名函数的,不然就无法确定到底是调用了哪一个函数。而在C++中的同名函数也是存在差异的,即他们的函数声明不完全相同,要么是参数列表或者是返回值的类型不同,亦或命名空间(namespace)。利用这种特性编译器在生成代码时将函数的返回值参数列表命名空间缝合成一个全新的名字在二进制代码中使用。

Currently, in my view,there are 两种类型的名称粉碎方式:微软的名称修饰(主要是MSVC编译器使用)和 Itanium的名称粉碎(gcc和clang编译器使用)。

4. Template模板

4.1 模板的基本内容

对于模板中的内容,我并不关心模板背后实现过程中的匹配问题,这是编译器应该考虑的。这里我只是列出了使用过程中需要注意的内容。

对于模板的定义需要使用关键字 typename 开头以及 作为参数化类型(parameterized type),:在C++98引入关键字 typename 之前这个位置是使用 class 的,所以在 template context 中这两者是等价的。

模板中有好多概念,如:具体化(specialization)、实例化(instantiation)和 隐式(implicit)、显示(explicit)。我们大部分使用的模板都是简单的使用方式,不会涉及到什么 显 / 隐实例化以及显示具体化

下面就是具体的例子来说明上面概念的组合。最常用的定义方式就是

template < typename T> void Swap( T, T);
这种方式进行的定义。

对于显示具体化 需要在 template 后添加 <>,而显示实例化则不需要。这些衍生的概念都是为了能够更好的进行模板匹配。而我们在使用时一般都会先进行实例化,如 std::vector< int > a;

template <typename T>	//same as class
void Swap(T, T);	//a normal template

template <>	//same as typename
void Swap<double>(double, double);	//a explicit specialization

template 
void Swap<char>(char, char);	//a explicit instantiation

4.2 类型和返回值问题

下面例子中是模板定义中一个常见的问题,变量类型的确定。由于并不知道T1T2 的具体类型所以下方代码中的 ?type? 就无法事先确定好。

template <class T1, typename T2>	//class is equal to typename in template scope
void func(T1 x, T2 y){
	...
	?type? xpy = x + y
	...
}

为了解决上述问题,在 C++11 中引入了一个新的关键字 decltype,这个关键字可以根据表达式自动来推导出类型。

double x = 5.5;
double &rx = x;
const double *pd;
decltype(x) w;	//w is type double
decltype(rx) u;	//u is type double &
decltype(pd) v;	//v is type const double *

另一个问题是函数返回值问题,这里返回值的类型 ?type? 应该是 decltype(x + y) 的类型,但是由于x + y声明是在 ?type? 后面,所以此时编译器无法使用 x + y 来进行类型推导。

template <typename T1, typename T2>
?type? func(T1 x, T2 y) {
	return x + y
}

为了解决上述问题,在C++ 11 中引入了后置返回类型(trailing return type),which 使用 auto 作为类型的占位符,同时在函数声明的后方使用 ->type 来指向返回值类型的定义。上述问题就可以按照下面的方式进行解决。

template <typename T1, typename T2>
auto func(T1 x, T2 y) -> decltype(x + y) {
	return x + y
}

4.3 std库中的模板

在C++的 std 库中有3种(我)常用的模板:std::string、std::vector和std::map。这里我 want 记录的不是如何使用他们,而是他们在二进制层面的特征。感谢 麦子zzy 提供的逆向内容。

std::string 不是模板,但它是一个非常重要的 std 类,所以我将还是决定将它的结构放置在这里。它自身有一个16字节的缓冲区,当字符串的长度小于15时会被放置在这个区域,而当字符串长度大于这个值时则会动态申请一片新的内存区域存放,使用 pStr 指针指向。

struct string {
	union {
		char buf[16];
		char* pStr;
	};
	std::size_t nLength;	//the length of the string
	std::size_t nCapacity;	//the capacity of the buf or pStr
};

std::vector 是 std 库中一个重要的模板,就数据结构而言它是简单的,简单到只使用3个指针就完成了所有内容。

struct vector {
	void* pFirst;	//pointer points to the fisrt element
	void* pLast;	//pointer points to the next of last element
	void* pEnd;		//pointer points to end of the buf
};

std::map,这是一个复杂的数据结构,背后的算法是红黑树。用到它的场景就是需要以 {key : value} 的形式来存放数据。它的核心就是下面的树节点。这个结构在逆向过程中最大的特点是在0x19(x64)0xd(x86) 偏移处会被按照 byte 被 assign 为 1。

struct TreeNode {					
	struct TreeNode* pLeft;			//0x00, left subtree, or smallest element if head
	struct TreeNode* pParent;		//0x08, parent, or root of tree if head
	struct TreeNode* pRight;		//0x10, right subtree, or largest element if head
	char bColor = 1;				//0x18,_Red or _Black, _Black if head  _Red = 0 ,_Black = 1
	char bIsnil = 1;				//0x19, true only if head (also nil) node
	struct Element ele;
};
  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值