第一题、预处理宏解析问题
#define MUL(a,b) a * b
printf("%d", MUL(1 + 2, 1 + 2));
考察的是宏解析后的代码是什么样子的:
MUL(a,b) 解析后为 a * b
a 转换为 1 + 2 、b转换为 1 + 2
a * b 转换为 1 + 2 * 1 + 2 = 1 + 2 + 2 = 5
掌握要领:宏 MUL( a , b ) 表示 MUL( 1 + 2 , 1 + 2 ),是代码编译层面上的,最终根据编译器的逻辑来转换宏的形态,证明如下:
a 表示 1 + 2,b 表示 1 + 2 a * b 表示 1 + 2 * 1 + 2
另外:宏描述其他用法:
1、分隔符: \ 用于连接上代码与下代码的描述,让代码更清晰
#define DEMO_STRING1 "1234567\
7654321"
#define DEMO_STRING2 "12345677654321"
DEMO_STRING1 == DEMO_STRING2
2、连接符:# 、##
#define STR_CONVERT(num) #num
STR_CONVERT(123) == "123"
#define STR_CONVERT1(num,oth) #num##oth
STR_CONVERT1(123,"4") == "1234"
#define STR_CONVERT2(a,b) a##b
int STR_CONVERT2(i,dex) = 1; == int idex = 1;
#define fua1(a,b) a##b
#define fub1(a) #a
#define begin(a) fub1(a)
puts(fub1(fua1(a, b)));
fub1() a = [fua1(a, b)] = #[fua1(a, b)] ==> "fua1(a, b)"
puts(begin(fua1(a, b)));
begin() a = [fub1(a)] = fub1() a = # 同时 fua1(a,b) ==> #a##b
#,是把#后面的内容看成字符串;
##,是连接前面的类型与后面的类型在编译前并起来;
第二题、字节对齐问题
1、32位环境下定义结构体
#pragma pack(1)
struct stu1
{
char a;
short s;
int i;
char str[5];
void* p;
};
#pragma pack()
一字节对齐:
struct stu1
char a; //1
short s; //2
int i; //4
char str[5]; //5
void* p; //4
stu1 的结构体长度为 16个字节
pragma pack(4)
struct stu2
{
char a;
short s;
int i;
char str[5];
void* p;
};
#pragma pack()
四字节对齐:
struct stu2
char a; [char(1) - - -] char 和 short 合并占3个字节,因下一个为4个字节的int,所以补位 1字节为 4字节
short s; [char(1) short(2) 补位(1)] 为 4字节对齐
int i; [int(4)] 为4字节
char str[5]; [char(5) 补位(3)] 为8字节
void* p; [void*(4)] 为4字节
stu2 的结构体长度为 20个字节
2、64位环境下定义结构体
#pragma pack(1)
struct stu1
{
char a;
short s;
int i;
char str[5];
void* p;
};
#pragma pack()
一字节对齐:
struct stu1
char a; //1
short s; //2
int i; //4
char str[5]; //5
void* p; //8
stu1 的结构体长度为 20个字节 ( * 64位程序指针表示8个字节 )
pragma pack(4)
struct stu2
{
char a;
short s;
int i;
char str[5];
void* p;
};
#pragma pack()
四字节对齐:
struct stu2
char a; [char(1) - - -] char 和 short 合并占3个字节,因下一个为4个字节的int,所以补位 1字节为 4字节
short s; [char(1) short(2) 补位(1)] 为 4字节对齐
int i; [int(4)] 为4字节
char str[5]; [char(5) 补位(3)] 为8字节
void* p; [void*(8)] 为8字节
stu2 的结构体长度为 24个字节
(备注:什么时候用1字节,什么时候用4字节:
当使用互联网传输的时候用1字节对齐结构能够得到精简且数据规整的格式
当内部调用运算时需要提高效率的时候用4字节对齐,4字节对齐不如1字节对齐节约空间,但是会提高读取效率。
有些平台:一个int型如果存放在偶地址开始的地方,那么一个读周期就可以读出32bit
而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果高低字节进行拼凑才能得到该32bit数据。
除了 float、double 以外:
如果是1字节对齐,CPU与内存的最小交换数据为1字节/次,所以 1 字节的变量是线程安全的。
如果是4字节对齐,CPU与内存的最小交换数据为4字节/次,所以 4 字节的变量是线程安全的。)
Lock();
int count = g_test/1024;
int mod= g_test%1024;
UnLock();
//如果不想加锁 可以写成
int temp = g_test;
int count = temp/1024;
int mod = temp%1024;
第三题、关于静态变量 static int i 在全局区的解读
int fun(int x)
{
static int i = 0;
if (x <= i)
{
i = -x;
}
else
{
i = x;
}
return i;
}
void main()
{
int a = fun(5);
int b = fun(3);
}
答案:fun(5); 输出为:_5_
fun(3);输出为:_-3_
解析路由:
int fun(int x) //第一轮 [1][ x = 5,i = 0] ↓ 第二轮 [2][x = 3 , i = 5] ↓
static int i = 0;
if (x <= i) // [1][ 5 <= 0 false ] ↓ [2][ 3 <= 5 true ] ↓
i = -x; // [2] [ i = -3 ]
else // [1] [ 5 > 0 true ] ↓
i = x; // [1] [ i = 5 ]
return i; // [1][i = 5] ↓ [2][ i = -3] ↓
第四题、关于数组定位 字节定位
int a[5] = {1,2,3,4,5};
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
[解析:a 的字节长度 等于 sizeof(int[5])] 指针位移的长度也为 sizeof(int(5))个字节
// *(a+1) 1, 2, 3, 4, 5
// a ↑
//a -> sizeof is 5 * int = 20
// 1, 2, 3, 4, 5 []
// ----------------
// &a
// 1, 2, 3, 4, 5 |_______________
// &a + 1
int* ptr = (int*)(&a + 1);
//1, 2, 3, 4, 5 | a, b, c, d, e
// ↑
// ptr
//1, 2, 3, 4, 5 | a, b, c, d, e
// ↑ ↑
// ptr - 1
printf("%d,%d", *(a + 1), *(ptr - 1));
&a == sizeof(int[5])
输出:*(a + 1) = 2、*(ptr - 1) = 5
第五题、多态虚拟继承[掌握调用顺序]
class A
{
public:
A(){ a = 5; }
virtual void fun(int x){ a += x; }
void fun2(int x){ a -= x; }
int a;
};
class B :public A
{
public:
B(){ a = 10;}
void fun(int x){ a -= x; }// fun 已实现 A的 fun 重载
virtual void fun2(int x){ a += x; }
};
void main()
{
A* p = new B;
p->fun(1); // 调用顺序 B : fun()
printf("%d", p->a); // 9
p->fun2(3); //调用顺序 A:fun2 [因为操作对象是A::fun2 没有和B::fun2 建立继承关系]
printf("%d", p->a); // 6
delete p;
p = NULL;
}
解析:virtual 是向下继承的 ↓
//a fun 无继承关系
//b virtual fun↓ 开始继承关系
//c fun 继承于b
//d fun 继承于b
创建者为B、调用对象为A,fun(1)由于继承关系,使用B::fun继承于A::fun重载作为函数对象;
fun2(3)由于多态是向下继承的,类A::fun2 与 B::fun2 是两个独立的函数,由A对象操作则使用A的调用;
第六题、遍历断链问题
调用存在运行bug,链路在遍历的过程中当前节点需要被删除,要把it移动到下一个节点,删除上一段数据
#include <map>
std::map<int, int> maplint;
void main()
{
for (int i = 0; i < 10; ++i)
{
maplint[i] = i;
}
for (std::map<int, int>::iterator it = maplint.begin(); it != maplint.end(); ++it)
{
printf("%d\n", it->second);
if (it->second % 2 == 0)
{
//std::map<int, int>::iterator ierase = it;//需要保留上一位
//++it; //推移
//maplint.erase(ierase); //删除上一个位置
//命题改写 返回it,再执行it+1,再执行 maplint.erase,到下一轮 再执行返回 it + 1
maplint.erase(it++);//0 -> 位移 1 遍历到 2; 2 -> 位移 3 遍历到 4 等等
}
}
}
第七题、C语言标准函数strcpy的实现
char * _strcpy(char* dst,const char* src)
{
//原始写法
//char* tmp = dst;
//while ((*dst++ = *src++) != '\0');
//return tmp;
//清晰写法
char* tmp = dst;
while (*src != '\0')
{
*dst = *src;
src++;
dst++;
}
return tmp;
}
void main()
{
char src[] = "administrator";
char dst[20] = "";
_strcpy(dst, src);
}
第八题、有序数组的二分查找算法(长度为len的升序数组arr中查找x)
int searchBin(int arr[], int len, int x)
{
int low, high, mid;
low = 0;
high = len - 1;
while (low <= high)
{
mid = (low + high)/2;
//printf("%d\n",mid);
if (x == arr[mid])
{
return mid;
}
else if (x < arr[mid])
{
high = mid - 1;
}
else
{
low = mid + 1;
}
}
return -1;
}
void main()
{
int arr[10] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int step = searchBin(arr, 10, 0);
}
解析:主要考的内容:从中定位,向上或向下查找
mid为指向数据的位置,根据数据的大小受low 和 high 的挤压移向上或向下的位置。
当指向某一个数据时,若该数比指向数小,排指向到最大的数据。
若该数比指向数大,排指向到最小的数据,直到找到后返回。