知识点记录


知识点记录

平时总是能遇到各种各样零碎的知识点,有的太小了没有写下来,有的则是太零碎了不知道怎么写下来,久而久之很多以前知道的也忘了,以前遇到过的问题又再次成为阻碍,那感觉实在难受。所以开一个笔记专门记录一些小的知识点,等有空再把知识点细化单独整理成博客。相当于作为博客的题材库。

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.垃圾回收算法

  1. 引用计数法

  2. Mark-Sweep法(标记清除法):一个优点就是可以避免循环引用,当A和B两个对象可能互相指向对方时,标记可以避免无限递归。缺点是会带来内存碎片。

  3. 三色标记法

  4. 分代收集

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.字节对齐

字节对齐基于三条原则:

  1. 数据成员对齐规则:结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,之后的每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机子上为4字节,所以要从4的整数倍地址开始存储)。
  2. 结构体作为成员:如果一个结构体里同时包含结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(如struct a里有struct b,b里有char,int ,double等元素,那么b应该从8(即double类型的大小)的整数倍开始存储)。
  3. 结构体的总大小:即sizeof的结果。在按之前的对齐原则计算出来的大小的基础上,必须还得是其内部最大成员的整数倍,不足的要补齐(如struct里最大为double,现在计算得到的已经是11,则总大小为16)。

文章作者: Philip
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Philip !
  目录