知识点记录
平时总是能遇到各种各样零碎的知识点,有的太小了没有写下来,有的则是太零碎了不知道怎么写下来,久而久之很多以前知道的也忘了,以前遇到过的问题又再次成为阻碍,那感觉实在难受。所以开一个笔记专门记录一些小的知识点,等有空再把知识点细化单独整理成博客。相当于作为博客的题材库。
C/C++
1.typedef与函数指针
typedef常见的用法
相当于是给类型起一个别名
typedef int myInt;
函数指针
返回类型(*函数名)(参数表)
#include <iostream> using namespace std; //定义一个函数指针pFUN,它指向一个返回类型为char,有一个整型的参数的函数 char (*pFun)(int); //定义一个返回类型为char,参数为int的函数 //从指针层面上理解该函数,即函数的函数名实际上是一个指针, //该指针指向函数在内存中的首地址 char glFun(int a) { cout << a << endl; return 'r'; } int main() { //将函数glFun的地址赋值给变量pFun pFun = glFun; //*pFun显然是取pFun所指向地址的内容,当然也就是取出了函数glFun()的内容,然后给定参数为2。 cout<<(*pFun)(2)<<endl; return 0; }
typedef 返回类型(*类型名)(参数表)
#include <iostream> using namespace std; //定义了一种新类型PTRFUN //这种类型表示指向某种函数的指针,这种函数以一个int为参数并返回char类型 //后面就可以像使用int,char一样使用PTRFUN来定义变量了。 typedef char (*PTRFUN)(int); //定义一个返回类型为char,参数为int的函数 char glFun(int a) { cout << a << endl; return 'r'; } int main() { //将函数glFun的地址赋值给变量pFun PTRFUN pFun = glFun; //*pFun显然是取pFun所指向地址的内容,当然也就是取出了函数glFun()的内容,然后给定参数为2。 cout<<(*pFun)(2)<<endl; return 0; }
2.枚举常量不能用做宏判断的条件
比如下面的代码
#include <iostream>
#define PLATFORM PS4
enum PLATFORM_TYPE {
SWITCH,
PS4,
};
int main()
{
#if PLATFORM == SWITCH //判断语句1
std::cout << "Nintendo!" << std::endl;
#elif PLATFORM == PS4 //判断语句2
std::cout << "Sony!" << std::endl;
#endif
return 0;
}
按照预期,如果#define PLATFORM SWITCH输出“Nintendo!”, 如果#define PLATFORM PS4则输出“Sony!”。
但是实际运行的结果总是“Nintendo!”,也就是判断语句1始终成立!
百思不得其解之后,我终于意识到条件编译是预编译阶段的事情,而enum的定义要等到编译阶段才会处理。也就是说在预编译阶段压根儿没有SWITCH和PS4这两个标识符。而且在 #if 之后,所有不能通过 #define 被替换为字面量的标识符和关键字都会被替换为0,因此也不会报错。所以实际上执行的判断语句是:
#if 0 == 0 //判断语句1
std::cout << "Nintendo!" << std::endl;
#elif 0 == 0 //判断语句2
std::cout << "Sony!" << std::endl;
#endif
所以才会有上面的现象。修改后逻辑终于正常了,代码如下:
#include <iostream>
#define PLATFORM PS4
#define SWITCH 1
#define PS4 2
int main()
{
#if PLATFORM == SWITCH //判断语句1
std::cout << "Nintendo!" << std::endl;
#elif PLATFORM == PS4 //判断语句2
std::cout << "Sony!" << std::endl;
#endif
return 0;
}
3.C/C++的头文件
保证同一个头文件不会被包含两次的方法有两个:
1.#ifndef
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
// 头文件声明、定义语句
#endif
这种方式受C/C++语言标准支持。
优点是它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的多个文件(或者代码片段)也不会被同时包含。
缺点是宏名冲突时会出现一系列奇怪的问题。
2.#pragma once
#pragma once
// 头文件声明、定义语句
#pragma once 一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,不适用于多个相同内容的文件。
优点是写法简洁,也不必再担心宏名冲突。
缺点是在存在多个相同内容的头文件时,还是会出现多次包含的情况。以及部分老的编译器可能不支持。
4.垃圾回收算法
引用计数法
Mark-Sweep法(标记清除法):一个优点就是可以避免循环引用,当A和B两个对象可能互相指向对方时,标记可以避免无限递归。缺点是会带来内存碎片。
三色标记法
分代收集
5.(void)变量
经常在一些驱动代码里面见到这种用法,变量前直接加(void)的语句,一开始对这种用法一头雾水,不知其何意!后经多方查阅资料了解,这只是一种防止编译器编译时报警告的用法。有些变量如果未曾使用,在编译时是会报错,从而有些导致编译不过,所以才会出现这种用法。而此语句在代码中没有具体意义,只是告诉编译器该变量已经使用了。
Lua的源代码中将其封装为一个叫做UNUSED的宏:
#define UNUSED(x) ((void)(x))
6运算符优先级
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 |
---|---|---|---|---|
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 |
() | 圆括号 | (表达式)/函数名(形参表) | ||
. | 成员选择(对象) | 对象.成员名 | ||
-> | 成员选择(指针) | 对象指针->成员名 | ||
2 | - | 负号运算符 | -表达式 | 右到左 |
(类型) | 强制类型转换 | (数据类型)表达式 | ||
++ | 自增运算符 | ++变量名/变量名++ | ||
– | 自减运算符 | –变量名/变量名– | ||
***** | 取值运算符 | *指针变量 | ||
& | 取地址运算符 | &变量名 | ||
! | 逻辑非运算符 | !表达式 | ||
~ | 按位取反运算符 | ~表达式 | ||
sizeof | 长度运算符 | sizeof(表达式) | ||
3 | / | 除 | 表达式/表达式 | 左到右 |
* | 乘 | 表达式*表达式 | ||
% | 余数(取模) | 整型表达式/整型表达式 | ||
4 | + | 加 | 表达式+表达式 | 左到右 |
- | 减 | 表达式-表达式 | ||
5 | << | 左移 | 变量<<表达式 | 左到右 |
>> | 右移 | 变量>>表达式 | ||
6 | > | 大于 | 表达式>表达式 | 左到右 |
>= | 大于等于 | 表达式>=表达式 | ||
< | 小于 | 表达式<表达式 | ||
<= | 小于等于 | 表达式<=表达式 | ||
7 | == | 等于 | 表达式==表达式 | 左到右 |
!= | 不等于 | 表达式!= 表达式 | ||
8 | & | 按位与 | 表达式&表达式 | 左到右 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 |
10 | | | 按位或 | 表达式|表达式 | 左到右 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 |
/= | 除后赋值 | 变量/=表达式 | ||
*= | 乘后赋值 | 变量*=表达式 | ||
%= | 取模后赋值 | 变量%=表达式 | ||
+= | 加后赋值 | 变量+=表达式 | ||
-= | 减后赋值 | 变量-=表达式 | ||
<<= | 左移后赋值 | 变量<<=表达式 | ||
>>= | 右移后赋值 | 变量>>=表达式 | ||
&= | 按位与后赋值 | 变量&=表达式 | ||
^= | 按位异或后赋值 | 变量^=表达式 | ||
|= | 按位或后赋值 | 变量|==表达式 | ||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 |
7.定义和声明
关于定义和声明的定义,C++primer的解释是这样的:
变量的定义(definition):用于为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且仅有一个定义;
变量的声明(declaration):用于向程序表明变量的类型和名字。
定义也是声明:当定义变量时我们声明了它的类型和名字。
可以通过使用extern关键字声明变量名而不定义它。
任何变量或函数等,都是要求先声明再使用。某些情况下,要使用的变量或函数还没有定义,则需要前向声明。
关于前向声明(forward declaration):
- 前向声明能显著缩短编译链接时间。
- 当两个定义互相使用对方时,需要使用前向声明打破循环。
- 在定义一个类型时如果用到了自身的指针时,需要使用前向声明。
8.字节对齐
字节对齐基于三条原则:
- 数据成员对齐规则:结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,之后的每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机子上为4字节,所以要从4的整数倍地址开始存储)。
- 结构体作为成员:如果一个结构体里同时包含结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(如struct a里有struct b,b里有char,int ,double等元素,那么b应该从8(即double类型的大小)的整数倍开始存储)。
- 结构体的总大小:即sizeof的结果。在按之前的对齐原则计算出来的大小的基础上,必须还得是其内部最大成员的整数倍,不足的要补齐(如struct里最大为double,现在计算得到的已经是11,则总大小为16)。