C/C++

1.排序
在这里插入图片描述
内排序:在排序过程中,整个数据处理过程都是在内存中,排序时不涉及数据的内、外存交换。
在这里插入图片描述
堆排序:nlog(n), 第一个 n 是指数据的总量,第二个 n 是指堆的容量。

归并排序:
时间复杂度:从底到顶每层合并是 O(N) ,然后二分的层数是 log N ,因此总共是 O(N log N)。
空间复杂度:对数组做归并排序的空间复杂度为 O(n),即新开辟数组为O(n),递归函数调用为O(logn)。

快速排序:最优的情况下空间复杂度为:O(logn) ,即每一次都平分数组;最差的情况下空间复杂度为:O( n ) ,即每一次只完成了一个数的排序。

复杂度分析
比较排序的时间复杂度最低为O(nlogn).

平均时间复杂度:
选泡插,
快归堆希桶计基,
n方n老n一三,
对n加kn乘k。(link)

快选希堆不稳定,
快桶希变为n方。

最坏时间复杂度:
快桶希,变为O(n*n)。其余算法不变。
不稳定排序:快速选择希尔堆。

stable_sort():稳定排序,不改变元素相对位置
自定义排序时,若传递引用,则需为 const 常量。
同样情况下, sort() 排序不需要传递 const 常量。

2.判断操作系统的位数
sizeof判断的是程序的位数,不同的编译器给出的结果不一样。

#include <windows.h>
#include <stdio.h>
int GetSystemBits()
{
    SYSTEM_INFO si;
    GetNativeSystemInfo(&si);
    if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ||
        si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64)
    {
        return 64;
    }
    return 32;
}

int main(int argc, char* argv[])
{
    const int nBitSys = GetSystemBits();
    printf(("run on %dbit System."), nBitSys);
 
    return 0;
}

3.单例模式:
1)单例类只能有一个实例。
2)单例类必须自己创建自己的唯一实例。
3)单例类必须给所有其他对象提供这一实例。
观察者模式:
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。例如,拍卖物品的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。

4.sizeof和strlen的值什么时候相等。

char a[] = { 'a','b' ,'c','d','\0' };
char* aa = a;
cout << sizeof(aa) << " " << strlen(aa) << endl;

// 整个数组所占的字节数             指针的大小
cout << sizeof(a) << " " << sizeof(&a) << endl;

指针的自增运算

int cd = 0;
cout << (cd++) << endl;//输出的依然是 cd 的值,而不是cd自增之后的值
                          // ()++的优先级高于 ()的优先级

int a[5][2] = { 0,1,2,3,4,5,6,7,8,9 };
int* p1 = a[1];
int(*p2)[2] = &a[1];

cout << sizeof(*p1) << " " << sizeof(*p2) << " " << sizeof(**p2) << endl; // 4 8 4
//会修改a[1][0]
cout << (*p1)++ << endl; // 优先级:()++、( *、++() )
cout << a[1][0] << endl; // 2 3

//地址的移动长度为 sizeof(*p1)
cout << *++p1 << endl; // 3
cout << *(1 + p1) << endl; // 4

// 地址的移动距离是 sizeof(*p2)、sizeof(**p2)
cout << **p2 << endl; // 3
cout << p2[2][2] << endl; // 8
int* a = new int[5];
int b[5];
cout << sizeof(a) << " " << sizeof(*a) << " " << sizeof(b) << " " << sizeof(&b);
// 64位编译器
// 8 4 20 8
delete []a;

const char* a = "abc";
cout << sizeof a << " " << strlen(a) << " " << sizeof("abc") << " " << strlen("abc") << endl;
// 8 3 4 3

5.用预处理指令#define声明一个常数,用以表明一年中有多少秒(不考虑闰年、闰秒)
1)#define语法的基本常识(不能以分号结束,括号的使用等等);
2)写出如何计算一年中有多少秒而不是计算实际的值,会更有价值;
3)意识到这个表达式将使一个int数溢出,因此最好用的长整形,那么你就会为自己加分了;
4)命名要规范,最好能表达相应的含义
#define SECOND_PER_YEAR (60* 60 * 24 *365)UL

int类型取值范围:
32位无符号整数,其表示范围是2的32次方,最大整数为2的32次方减1;
有符号数则要去除一个符号位,正数最大为2的31次方减1 , 负数最小为负的2的31次方 。(link)

6.auto是一个占位符,在编译期会被替换为相应的类型。
使用auto时,必须初始化变量。
auto不能用于函数形参、类的非静态成员。
auto不能用于定义数组,不能用于模板参数。

在lambda表达式中,auto可用于形参。

7.不能以返回值区分不同的函数
1)

int fun(){}
double fun(){}
fun();//编译器无法确定调用哪个函数

2)编译器在编译函数时,并不把函数返回值作为编译信息。
3)编译器会进行隐式转换。

8.注意项

int a = 1, b = 10;
scanf("%d",a);
// scanf("%d",&a);
do 
{
     b -= a;
     ++a;
} while (b-- < 0);
cout << b << " " << a << endl;

union ux
{
    short a; // int a;
    char c[2];
};

ux r;
r.c[0] = 10;
r.c[1] = 1;
cout << r.a << endl;

9.C和C++的区别
C++:面向对象编程、类(封装、继承、多态)、new/delete、字符串类、输入输出流、重载、变量定义位置、引用、智能指针、nullptr、auto等。

10.野指针和悬空指针。

11.RTTI
C++是一种静态类型语言。其数据类型是在编译期就确定的,不能在运行时更改。然而由于面向对象程序设计中多态性的要求,C++中的指针或引用本身的类型,可能与它实际代表(指向或引用)的类型并不一致。有时我们需要将一个多态指针转换为其实际指向对象的类型,就需要知道运行时的类型信息,这就产生了运行时类型识别的要求。

12. i++: 是右值,因为返回的是临时变量

const int  int::operatorint{
int oldValue = *this++*this);
return oldValue;
}

++i: 是左值

int&  int::operator++()
{
*this +=1return *this}

13.静态链接和动态链接
静态链接:
链接器在链接静态链接库的时候是以目标文件为单位的。比如程序引用了静态库中的printf()函数,那么链接器就会把库中包含printf()函数的那个目标文件链接进来,如果此目标文件包含其他未引用到的函数,那么这些函数同样会被链接进最终的可执行文件之中。
动态链接:
把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件(link)。

PIC( position independent code):
使用共享库的一个关键目的是为了使多个进程能够共享内存中的同一份代码拷贝,以达到节约内存资源的目的。比较好的方法是将动态库编译成可以在任意位置加载而无需链接器进行重定位的代码,即位置无关码—PIC.
主要原理:
无论内存在何处加载目标模块,数据段和代码段的距离总是保持不变的。因此,代码段中的任意指令与数据段中的任意变量之间的距离在运行时都是一个常量,而与代码和数据加载的绝对内存位置无关。

14.常引用 const int& a; 常量的引用
指针常量 int* const a; 指针是一个常量
常量指针 const int* a; 常量的指针

15.时间局部性:是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。
空间局部性:是指一旦程序访问了某个存储单元,则不久之后其附近的存储单元也将被访问。

16.const实现原理

const int con_var = 3;
int* b = (int*)&con_var;
*b = 5;
printf("%d", con_var);
//在 C语言中,输出结果为 5  在 C++中,输出结果仍为 3
//对于全局常量,不论是C还是C++,程序在运行阶段都会停止运行,显示内存访问冲突。

字符串常量会被放在一个专用的字符串池内存块中,有些编译器还会将其放在代码区(视所用的编译器和编译选项而定), 字面常量不占内存空间,例如字面常量 1,编译器会将其处理为立即数(在立即寻址方式的指令中给出的数,对于较小的数,立即数会被直接放在指令里)。
对于局部常量(const变量),编译器一般将其放在栈区。对于全局常量,编译器一般将其放在只读数据段,并且不能通过取地址来修改该变量,因为变量所在的内存块只允许读,不允许修改。

当代码中直接使用全局或局部常量(即该常量被直接作为右值使用)时,编译器会将其直接替换成初始化时的值。这样一来,即使通过地址操作修改了常量的值(全局常量的值不可被修改),也不会影响常量的使用。

17.printf函数的实现原理(可变参数函数) (17、61、63、88)
对于32位系统,编译器会将函数参数放入栈中;对于64位系统,编译器会将前6个参数放入6个寄存器中,余下的参数放入栈中(link)。
因此,二者的可变参数函数的实现原理并不相同。
对于32位系统,函数形参均被放入栈中,且是连续入栈的,因此当前参数后面紧跟着便是下一个参数。这样便可获取所有的参数。
对于64位系统,编译器会创建一个称为参数保存区的栈空间,并将前6个参数对应的寄存器中保存的值保存在参数保存区,其他参数放在栈中(link)。

18.内存碎片如何处理(在程序需要分配连续的内存空间的时候,只需要在虚拟内存空间分配连续空间,而不需要实际物理内存的连续空间,可以利用碎片)

19.输出结果为:8 3 4 3

 const char* a = "abc";
 cout << sizeof a << " " << strlen(a) << " " << sizeof("abc") << " " << strlen("abc") << endl;

20.如下,当形参为指针的引用时,若将局部变量作为实参传入函数,则引用需加上const限定,否则编译不通过。
但是若将全局变量作为实参传入函数,则引用不需加上const限定。

当形参为普通类型的引用,如int类型,则没有上述限制。

void fun(int* const & a)
{
    int b = 9;
    cout << b << endl;
}
int main(int argc, char* argv[])
{
    int a = 5;
    int& b = a;
    int c[2] = { 0,0 };
    fun(c);
	return 0;
}

21.内存分区
命令行参数和环境变量位于内存中的高于栈地址的地址处。
全局(静态)存储区:存放全局变量和静态变量。包括DATA段(全局初始化区)和BSS段(全局未初始化区)。其中,初始化的全局变量和静态变量存放在DATA段,未初始化的全局变量和未初始化的静态变量、初始化为0的全局变量和静态变量存放在BSS段。程序结束后由系统释放。 其中BSS段的特点是:在程序执行之前BSS段会自动清零。所以,未初始化的全局变量和静态变量在程序执行之前已经成0了。数据段和BSS段是可读写的,代码段是只读的。
heap(堆): 当进程未调用malloc时是没有堆段的,只有调用malloc时才分配一个堆(仅是初始化虚拟内存布局,申请一大块内存作为内存池),并且在程序运行过程中可以动态增加堆大小(移动break指针),从低地址向高地址增长。
代码段分为文本区和只读存储区,文本区存放的是机器代码

一个可执行程序在存储(没有调入内存)时分为代码段、数据区和未初始化数据区(BSS段)三部分。text段和data段在编译时已经分配了空间,而BSS段并不占用可执行文件的大小,它是由链接器来获取内存的(link)(link)。

将数据区分为数据段和BSS段的目的是节约磁盘空间,BSS段的数据所占用的磁盘空间不是实际所要求的空间尺寸。上述数据被存放在可执行文件中,可执行文件被存放于磁盘中。
当可执行文件加载运行时,操作系统会将BSS段中的变量全部初始化为0,由链接器从可执行文件中获取BSS段所需要的内存大小,并进行内存分配。
在段表中使用一个段描述符,该段描述符的size属性记录BSS段存储的所有变量的大小总和。
BSS段中每个变量的符号被记录在符号表中,每个变量的大小,被存储在符号表的 size 属性中。

静态变量存储在虚拟地址空间的数据段和bss段,C语言中其在代码执行之前初始化,属于编译期初始化。而C++中由于引入对象,对象生成必须调用构造函数,因此C++规定全局或局部静态对象当且仅当对象首次用到时进行构造

栈区并不是在运行时才被创建:
1)当创建线程的时候,操作系统为每一个线程分配栈空间。通常情况下,当首次动态分配内存时,堆被创建。
2)栈附属于线程,因此当线程结束时栈被回收。当应用程序(进程)退出时,堆空间被回收。
3)当线程被创建的时候,栈的大小被固定。程序运行时,可以动态更改堆的大小。

22.weak_ptr
在这里插入图片描述
23.乘法与除法的原理
二进制除法
取余运算,本质上是除法运算,得到商的同时也会得到余数。

24.内存泄漏不仅是堆内存泄漏。包含堆内存泄漏、系统资源泄漏、没有将基类的析构函数定义为虚函数。
栈溢出:递归层数过多、向数组等数据结构写入的数据的字节数高于其申请的字节数。

25.memset为逐字节赋值
C中为<memory.h> 或 <string.h>
C++中为< cstring >
借助动态分配内存来表示二维数组。
释放二级指针动态申请的内存。

 //借助动态分配内存来表示二维数组
        int** dp=new int*[m+1];
        for(int i=0;i<m+1;++i)
        {
            dp[i]=new int[n+1];
            memset(dp[i],0,(n+1)*sizeof(int));
        }
        {do something}

		for(int j=0;j<m+1;++j)
			delete [] dp[j];
		delete [] dp;

26.因为在联合体中,变量总是从低地址开始存储,所以才能借助联合体来判断大端、小端。(link)

27.回调函数(link)(link)

阻塞式回调(同步回调):回调函数的调用发生在起始函数返回之前。

延迟式回调(异步回调):延迟式回调需要多线程或者多进程才能实现,回调函数的调用可能发生在起始函数返回之后。

C语言实现回调函数、多态

28.double类型除法运算与float类型除法运算(875题)

cout<<ceil(1.0*312884470/312884469)<<endl;//double,结果为2   
cout<<ceil(1.f*312884470/312884469)<<endl;//float,结果为1
cout<<(312884470+312884469-1)/312884469<<endl;//结果为2

29.无其他关键字时,在 { } 中的语句块仍为局部语句块。

{
	int abc = 5;
}
	std::cout << abc << std::endl;//在语句块外,abc为未定义标识符

30.模板函数的声明与实现最好放在同一个文件里(link)。
若将模板函数的声明放在.h文件中,将模板函数的实现放在另一个.cpp文件中。而后在main.cpp中仅#include 头文件,且main.cpp里调用了模板函数,则编译器会报错:error LNK2019: 无法解析的外部符号。
此时,若在另一个.cpp文件中对模板函数进行同样的调用,则也可以编译通过。

模板函数优点、缺点:
优点 :
编写一个模板,就可以在实例化的时候,由一个模板解决不同类型数据所产生的相同问题。很好的实现代码的重用。
缺点:
1)模板的数据类型在编译时才能被确定。因此,模板函数的声明与实现最好放在同一个文件里。
2)因为模板最近才被加入C++标准中,所以有些C++编译器还不支持模板,当使用这些编译器编译含有模板的代码时就会发生不兼容问题。
3)因为模板函数会被编译两次,所以会带来额外的开销。

31.字节对齐(link)(link)
1)结构体变量的首地址能够被其对齐字节数大小所整除。
2)结构体每个成员相对结构体首地址的偏移都是当前成员大小的整数倍,如不满足,对前一个成员填充字节以满足。
3)结构体的总大小为结构体最大成员大小的整数倍,如不满足,最后填充字节以满足。
若使用了编译器指令:#pragma pack(n),则取 n 和结构体最大成员所占字节数的较小值作为字节对齐数。
若是“按1字节对齐”,则所有成员在分配内存时都是紧接在前一个变量后面依次填充的。

struct A{
    bool a;
    bool b;
    bool c;
    int i;
    bool d;    
};

struct B{
    bool a;
    bool b;
    bool c;
    bool d;
    int i;
};
// 12byte 8byte

32.对于插入排序,在遍历有序数组时,若是从前向后遍历,则每一次都要遍历到待插入元素原先所处的位置(因为还要移动元素),但是若从后向前遍历,则每一次所要遍历的元素的数目相对较少,因为只需遍历到待插入的位置。

33.智能指针计数器使用 int* count;
1)若使用 static int m_pCount;,则在一个new资源块下可以实现计数功能。若出现多个new资源块时,将无法实现多个资源块的正确释放。
因为智能指针是与申请的资源块绑定的,所以当多个智能指针绑定到不同的资源块上时,若计数器是静态成员变量,则所有的资源块的计数器是相同的。
2)若使用int m_pCount;则无法同步一个资源块下的多个指针对象的计数器。

34.std::move()函数
将左值转化为右值引用。常用于实现移动语义。

35.enable_shared_from_this (link)
当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr。
(link)
若要在类内部传递this,请考虑从enable_shared_from_this继承
若从enable_shared_from_this继承,则类对象必须让shared_ptr接管。

shared_ptr作为函数参数进行传递时,最好使用值传递。因为在函数里可能会释放对象(link).

unique_ptr 不支持拷贝构造,所以作为函数参数传递时,不能用值传递。如果需要转移所有权,则调用std::move(),否则传递所管理的对象。

std::unique_ptr<int> ab;
ab = std::unique_ptr<int>(new int(5));

std::unique_ptr<int> ac(new int(5));
ab = std::move(ac);
// ab=ac;//操作被禁止

shared_ptr 是不能指向数组的,因为 shared_ptr 的析构函数默认是调用 delete 的,如果指向数组,则析构时,只会删除数组的一个元素。若要指向数组,则需要重定义析构函数。

36.volatile
为了防止一个变量在意想不到的情况下被改变,我们会将变量定义为volatile,这就使得编译器不会"自作主张"的去“动”这个变量的值了。准确点说就是每次在用到这个变量时必须重新从内存中直接读取这个变量的值,而不是使用保存在寄存器里的备份。
因为在某些情况(如编译器发现程序不会更改某个变量的值)下,编译器可能会进行优化,将变量的值放在寄存器里。在之后的操作中,如果再次使用此变量的值,就直接操作这个寄存器,而不去读取内存中保存的值,因为读取寄存器的速度要快于直接读取内存的速度。
mutable
1)const 成员函数保证不修改对象,即不修改对象的任何部分,包括对象的所有数据成员. 但是,有时候在函数中,对象的某个数据成员需要发生变化,比如计数。这时,可用mutable,用于说明:修改此变量不影响const语义,所以不需要禁止 const 函数修改它。否则,编译器会禁止函数对其进行修改。

// const 成员函数保证不修改对象,即不修改对象的任何部分,包括对象的所有数据成员
void A:func() const {}
mutable int num=0;

2)Lambda 表达式中的 mutable. 按值捕获(Caputre by Value)的方式不允许程序员在 Lambda 函数的函数体中修改捕获的变量。而以 mutable 修饰 Lambda 函数,则可以打破这种限制。但是此时修改的是函数体内的变量,原变量不受影响。

37.const与#define(link)
定义的性质、起作用的阶段、起作用的方式、存储位置、类型检查、调试、再定义、头文件重复引用。

38.整数常量默认是int类型
用科学计数法表示的字面量,默认类型是double类型

double a = 2 + 3 / 2;
int ab = 2 + 3 / 2;
double b = 2 + (1e2+1) / 2;
std::cout << a << " " << ab << " " << b << std::endl;
// 输出  3 3 52.5

39.B树以及数据库索引为何不用BSTB+树
数据库索引是被存放在磁盘中的。B树相较于BST,树的高度较小,因此磁盘IO次数会更少。

B+树相较于B树:
1)B+树的中间节点仅存放索引,不存放数据,因此同样大小的磁盘页可以存放更多的元素。这也意味着B+树的高度会更低,因此磁盘IO次数会更少。
2)由于在B+树中,仅有叶子节点用于存放数据,因此每次查询,都要查询到叶子节点。这就使得B+树的查询性能比较稳定。而B树的查询性能并不稳定,因为B树的中间节点也存放了数据。
3)在B+树中,所有数据值都是按键值的大小顺序存放在同一层的叶节点中,各叶节点之间通过指针进行连接。因此,易于进行范围查询。

数据库索引为什么不使用哈希表:
1)不能进行范围查询。
2)不支持排序,因为哈希表的索引是散列值,而非数据的原本值。
3)当数据量较大时,可能会产生比较多的哈希冲突。

40.数据量较大的算法题
BitMap用于排序
1、2、6、7 题。
1)要先采用哈希算法计算每个IP的相应的散列值,这样的话,所有相同IP地址会被映射到同一个文件(link).
3)堆的容量为100,因为要求返回的词的数量是100.
如果要选出TOP100,则划分的每个子文件,都要先选出TOP100。

大数据,寻找中位数

41.memmove()函数比memcpy()函数更安全,函数实现:(link)

42.当满二叉树(按照国内定义)仅在最后一层连续缺失若干个右边的叶子节点时,就变成了完全二叉树。
哈夫曼树满足国际上定义的满二叉树,但是不满足国内的定义。

43.强引用、弱引用
强引用:当至少有一个强引用时,对象就不能被释放。shared_ptr就是如此。
弱引用:它仅仅是对象存在时候的引用,当对象不存在时弱引用能够检测到,从而避免非法访问,弱引用也不会修改对象的引用计数。

44.C语言不支持函数重载,是因为底层无法区分不同参数的函数。
但是可通过可变参数或函数指针来模拟函数重载(link)。

45.一个函数只有声明没有实现而且被调用,会在链接阶段报错——“无法解析的外部符号”。

46.检测无限循环的位置
在可能的语句块中放置一个static long变量,将该变量初始化为0,之后每进入一次该语句块,变量自增一次。当该变量的值到达定义的一个很大值时判断为无限循环,终止程序。

47.malloc分配了1G内存,物理内存4G,还剩多少G?
如果不访问相应内存数据的话,则物理内存不会发生变化。因为malloc申请内存时,最初仅是建立一个虚拟内存布局,只有当真正访问到相应数据时,才会通过缺页中断将相应的数据调入内存。

堆、栈内存大小的限制.

48.C++如何用O(1)的时间交换两个std::string字符串?
直接交换两个字符串的地址,而不去复制元素。

49.左移、右移不会改变元素自身的值

	int x = 4;
	std::cout << (x >> 3) << " " << x << " " << (x << 3);
	// 0 4 32

50.void* 不能直接输出值,因为指针所存放的仅是数据的起始地址,需要知道数据的类型,才能知道需要输出起始地址之后多少字节的数据。而 void 是空类型,并未指定数据长度,可充当“接口”,强制转换为其他类型的指针。

51.什么时候需要重载 new 运算符:需要检测内存泄漏时、统计对象的内存占用、想按照自己的方式来分配内存(比如一次仅分配 20 byte)。
在类内重载的operator new、operator delete函数,默认为 static 函数。
重载方式分为在类内重载和全局重载,前者仅会影响特定类的对象,而后者会影响所有使用 new、delete的变量(link)。

new:首先调用operator new来分配内存,而后在分配的内存上,调用构造函数来初始化对象。
其中,第二步也可以通过 placement new来实现(link)。
placement new是operator new的重载之一,如果传入的内存不是堆内存,则不能手动释放内存。但是需要手动调用析构函数。

52.C、C++中的 struct 的区别
1)C中的 struct 是一些变量的集合体,用于封装数据,不涉及算法和操作。而C++中的 struct 是把数据变量及对这些数据变量的相关算法和操作给封装起来。
2)C中 struct 只是类型声明,没有内存空间的分配,而 static 变量是需要分配内存的(编译期分配内存)。所以不能在结构体中声明/定义静态变量。C++中struct可以定义静态函数,声明静态变量,与类相同。
3)C中 struct 内,不能初始化成员变量,不能指定成员变量存储类型(自动的(auto)、静态的(static)、寄存器的(register)和外部的(extern))。
4)C中的 struct 是没有权限设置的。C++中struct增加了访问权限,且可以和类一样有成员函数。
5)C语言没有“继承”,因此C中的 struct 不可继承。
6)在C中使用结构体时需要加上struct,或者对结构体使用typedef取别名,而在C++中可直接使用。

C++中结构和类的区别
1)struct 默认访问属性、继承方式是 public 的,而 class 默认的访问属性、继承方式是 private 的。
2)结构继承类时,默认继承方式是 public,类继承结构时,默认继承方式是 private. 即默认继承方式取决于继承的一方。
3)class 可用于定义模板参数,类似于typename。但是 strcut 不能用于定义模板参数。
4)如果数据成员的访问属性都是public,则对于类和结构,下述 { } 赋值都是可行的。前提是不显式定义构造函数。定义其他成员函数都是可以的,包括析构函数。

struct st {
	int sy;
	int hc;
	
	//st() {};
	void st1() {};
};

int main(int argc, char *argv[])
{
	st stt = { 1,2 };
	std::cout << stt.sy << " " << stt.hc << std::endl;
}

53.仅声明变量,是不会分配内存的。但是声明并初始化(此时也可称为变量的定义),会分配内存。

extern int a;//表明变量a在其他地方有定义,此处仅为a的声明
int b;//是变量的声明,也是变量的定义,此时编译器会随机赋予一个初值

extern int c=1;//是变量的声明,也是变量的定义
int c=1;//当两条语句处于同一个作用域时,属于变量重定义
	  //例如当第二条语句在主函数中,第一条语句在主函数外时,则不属于变量重定义

54.C++防止头文件被重复引入的3种方法(link).

55.模板的实现(link)
C++对函数模板进行了两次编译,第一次编译仅仅生成一个函数头,第二次编译则是在函数调用时根据模板的类型参数列表具体的实现这个模板对应的类型的函数实例,注意这里是依据类型参数列表来实现,而不是依据调用次数。所以编译器并不是把函数模板处理成能够处理任意类的函数。

56.inline 和宏的区别(link)
内联函数必须在头文件中定义(link)(link)

57.命令行本身也是一个程序,因此通过命令行调用外部 exe 文件时,工作目录是命令行最前端的目录(可通过 cd 命令切换目录)。
在这里插入图片描述
VS的工作目录并不是 exe 文件所在的目录,而是 xxx.vcxproj 所在的目录。

如果是在VS中执行程序,则相对路径的起点是当前程序的工作目录。如果是通过其他程序来调用,则相对路径的起点是发起调用的程序的工作目录。

58.在C语言中,函数和初始化了的全局变量为强符号(Strong Symbol),未初始化的全局变量为弱符号(Weak Symbol)。强符号之所以强,是因为它们拥有确切的数据,变量有值,函数有函数体;弱符号之所以弱,是因为它们还未被初始化,没有确切的数据。强符号和弱符号都是针对定义来说的,比如extern int a;不是强/弱符号,但extern int b=0;属于强符号。
链接器会按照如下的规则处理被多次定义的强符号和弱符号:
1)不允许强符号被多次定义,也即不同的目标文件中不能有同名的强符号;如果有多个强符号,那么链接器会报符号重复定义错误。
2)如果一个符号在某个目标文件中是强符号,在其他文件中是弱符号,那么选择强符号。
3) 如果一个符号在所有的目标文件中都是弱符号,那么选择其中占用空间最大的一个。

59.对于数组,快排的速度高于归并排序。对于链表,归并排序更快。(link)
在这里插入图片描述
60.warning C4150: 删除指向不完整“B”类型的指针;没有调用析构函数
因为在 tool.h 中,没有B的定义,除非加上相应的头文件,或者添加定义。

// demo.h
#pragma once
class B
{
	int a;
};

// tool.h
#pragma once
//#include "demo.h"  
 class B;
 class A
 {
 private:
	 B* b;
 public:
	 ~A() {
		 delete b;
	 }
 };

// main.cpp
#include "tool.h"
#include "demo.h"
int main()
{
	A* a = new A;
	delete a;
	return 0;
}

61.参数入栈顺序为什么是从右向左(link)(link)
为了支持可变长参数。(17、61、63、88)
在可变参数函数的一般形式中,从左边开始的某几个参数是已经确定的参数,右边省略号代表未知参数部分。对于已经确定的参数,它在栈上的位置也必须是确定的。否则意味着已经确定的参数是不能定位和找到的,这样就无法保证函数正确执行。衡量参数在栈上的位置,就是距离确切的函数调用点(call f)有多远。已经确定的参数,它在栈上的位置,不应该依赖参数的具体数量,因为参数的数量是未知的!
因此,需要使得已经确定的参数,距离函数调用点有确定的距离。要满足这个条件,只有参数入栈遵从自右向左规则。 也就是说,左边的确定的参数后入栈,这样的话距离函数调用点就有确定的距离(最左边的参数最后入栈,离函数调用点最近)。

当函数开始执行后,它能找到所有已经确定的参数。之后根据函数自身的逻辑,来负责寻找和解释后面可变的参数(在离开调用点较远的地方),通常这依赖于已经确定的参数的值(例如prinf()函数的格式解释符)。

62.调试时的断点的原理(link)(link)
软件中断:指令INT 3 ,当程序执行到INT 3指令时,会引发软件中断。操作系统的INT 3中断处理器会寻找注册在该进程上的调试处理程序。比如VS的调试器。
硬件中断:X86系统提供8个调试寄存器(DR0-DR7)和2个MSR用于硬件调试。其中前四个DR0~DR3是硬件断点寄存器,可以放入内存地址或者IO地址,还可以设置为执行、修改等条件。

63.函数的调用过程(link)(link).
在调用函数时,(17、61、63、88)
1)把EAX,ECX和EDX压栈。这是一个可选的步骤,只在这三个寄存器内容需要保留的时候执行此步骤。
2)将函数参数从右到左依次入栈。 (37,函数参数并非都是保存在栈中)
3)再调用 call 指令,执行对应的函数。当call指令执行的时候,EIP指令指针寄存器的内容会先被压入栈中。因为EIP寄存器是指向main中的下一条指令,所以现在返回地址就在栈顶了。

假设函数A调用函数B,我们称A函数为”调用者”,B函数为“被调用者”,则函数调用过程可以这么描述:
1)先将调用者(A)的栈的基址(ebp)入栈,以保存之前任务的信息,函数返回之后可以继续执行之前的操作。
2)然后将调用者(A)的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者B的栈底)。
3)然后在这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间,进行函数入参的压栈等操作。
4)函数B返回后,当前函数B的栈帧的ebp即恢复为调用者A的栈顶(esp),使栈顶恢复为函数B被调用前的位置;然后调用者A再从恢复后的栈顶可弹出之前的ebp值(可以这么做是因为这个值在函数调用前一步被压入堆栈)。这样,ebp和esp就都恢复了调用函数B前的位置,也就是栈恢复函数B调用前的状态。

在程序控制权返回到调用者后,传递给被调函数的参数已经不需要了。此时需要将所有传递给被调函数的参数弹出栈,实现堆栈平衡,即使得栈区恢复原样。

栈帧(Stack Frame):
每一次函数的调用,都会在调用栈上维护一个独立的栈帧(stack frame)。
每个独立的栈帧一般包括:
函数的返回地址和参数;
临时变量: 包括函数的非静态局部变量以及编译器自动生成的其他临时变量;
函数调用的上下文;
栈是从高地址向低地址延伸,一个函数的栈帧用ebp 和 esp 这两个寄存器来划定范围.ebp 指向当前的栈帧的底部,esp 始终指向栈帧的顶部;
ebp 寄存器又被称为帧指针(Frame Pointer);
esp 寄存器又被称为栈指针(Stack Pointer)。

SS:存放栈的段地址;
SP:栈寄存器SP(stack pointer),存放栈的偏移地址;
BP: 基数指针寄存器BP(base pointer)是一个寄存器,和栈指针SP联合使用,为了SP校准而使用,用于寻找栈里的数据。
比如,栈中压入了很多数据或者地址,想通过SP来访问这些数据或者地址,但SP是要指向栈顶的,不能随意更改,这时候就需要使用BP,把SP的值传递给BP,通过BP来寻找栈里的数据或者地址。
SP,BP一般与段寄存器SS 联用,以确定栈中某一单元的地址,SP用以指示栈顶的偏移地址,而BP可作为栈区中的一个基地址,用以确定在栈中的数据的地址。

64.函数指针、lambda表达式
编译器将 lambda 表达式转换为函数对象,然后通过函数对象来调用(link)。
auto 推导出来的类型与对象的实际调用方式有关。

// padd是函数对象,而非函数指针
auto padd = [](int a, int b)->int {return a + b; };
//光标移动到 auto ,显示出其推导类型是 class lambda int(int a, int b)->int
// padd 类型是 int padd(int a, int b)
//std::cout << padd << std::endl; //所以编译器会报错
std::cout << padd(1, 3) << std::endl;

int (*ao)(int, int);
ao = [](int a, int b)->int {return a + b; };
std::cout << ao(2, 1) << std::endl;

// ao保存的应该就是 lambda表达式对应的函数对象的地址
std::cout << ao << " " << &padd << " " << &ao << std::endl;
int fun(int a)
{
	return a;
}
auto p=fun;
// 编译器推导出的 p 的类型是 int(*p)(int),即函数指针

65.指针的初始化

int* a=nullptr;
int* b=a;//此时是初始化,把 a 的值拷贝一份,用于初始化 b
		//b所存储的并不是a的地址

66.读取以逗号间隔的数据
cin读取输入流,不会读取换行符

std::vector<int> vec;
int v;
while (std::cin >> v)
{
	vec.emplace_back(v);
	if (std::cin.get() == '\n')
		break;
}
std::cout << vec.size() << std::endl;
for (int i : vec)
	std::cout << i << " ";

67.取余运算仅是对于整型而言, long、long long也是整型。
因此对于double、float等类型,不能进行取余运算。
long的字节数不小于int的字节数,int的字节数不小于short的字节数。
long long最小是8字节。long的字节数不一定是8字节。

68.stoi 会进行范围检查。
位于 #include < string > 头文件中。

string str="2021,string";
stoi(str.substr(0,4));// 2021 [start,length]
std::string::size_type sz;   
stoi(str,&sz); // 2021
str.substr(sz);// ,string 从start开始直到末尾元素

stoi("012");// 12
stoi("002");// 2

69.宏定义,替换完成之后,再进行计算。

#define sum(a,b)a + b
std::cout << sum(sum(2, 5), sum(4, 10)) * sum(2, 3); //宏定义

70.零长度数组,不占用空间,定义于结构体中,也不占用空间。主要用于构造可变长结构体。
不是C/C++ 标准,属于编译器自身的扩展。
在一个可变长结构体中,零长度数组不占用结构体的存储空间,数组名是地址,因此如果为结构体对象申请额外的内存,便可以通过使用结构体的成员去访问所申请的额外内存。

struct buff {
    int len;
    char a[0];
};
int main()
{
	buff* buf;
	buf = (buff*)malloc(sizeof(struct buff) + 20);
	buf->len = 20;
	strcpy_s(buf->a, sizeof("hello!\n"), "hello!\n");
	puts(buf->a); //输出 hello!
	free(buf);
	return 0;
}
std::cout << sizeof("hello!\n"); //输出 8, 换行符也占用空间

71.可以在函数中声明另一个函数,但是不允许定义,以防函数递归调用。

struct buff {
  buff(){std::cout<<"buff.\n";}
};
int main()
{
	buff b();//此为函数声明
	buff buf;//此为定义结构体对象
	
	const type_info& nInfo = typeid(b);
	std::cout << nInfo.name() << " | " << nInfo.raw_name() << " | " << nInfo.hash_code() << std::endl;
	// struct buff __cdecl(void) | .$$A6A?AUbuff@@XZ | 6628118117994266932
	return 0;
}

72.long long类型后缀 LL、ll, unsigned long long类型后缀ULL、Ull、uLL、ull.
0为 int 类型常量,0LL为long long 类型常量。

73.malloc实现原理

74.递归调用lambda函数

std::function<int(int)> factorial = [&](int i)
{
	return (i == 1) ? 1 : i * factorial(i - 1);
};
cout << factorial(4) << endl;

const auto& sum = [&](auto& s, int i)->int
{
	return (i == 1) ? 1 : i * s(s, i - 1);
};
cout << sum(sum,4) << endl;

在lambda表达式中,auto可用于形参。

75.可以动态分配位于栈上的内存,方法是使用布局new运算符。但布局new运算符,不能使用delete释放内存。
若使用布局new运算符为类的对象分配内存,则可通过显式调用析构函数来释放对象的内存。

76.返回值优化(RVO)
仅对于匿名对象,可以进行优化。
当函数需要返回一个对象的时候,如果自己创建一个临时对象返回,那么这个临时对象会依次调用一个构造函数、一个复制构造函数以及一个析构函数。而如果进行优化,就可以仅调用一个构造函数,这样就省去了一次拷贝构造函数的调用和一次析构函数的调用,如下第一种情况的2)。
C++编译器默认使用RVO。

class A
{
public:
	A() { cout << "Construct A" << endl; }
	A(const A& t)
	{
		cout << "Copy constructor." << endl;
		 this->a = t.a;
	}
	A& operator = (const A& t)
	{
		cout << "assignment." << endl;
		this->a = t.a;
		return *this;
	}	
private:
	int a = 0;
};
A fun()
{
	A k;
	return k; // 1)
	//return A(); 2)
}
int main()
{
	A v = fun();// 1)调用构造函数初始化k,调用拷贝构造函数初始化v
				// 2)直接调用构造函数在v的内存位置进行构造
	A y;
	y=fun();//1)调用构造函数初始化 y、k,调用拷贝构造函数使用k初始化临时对象,调用重载赋值运算符给y赋值
		   //2)调用构造函数初始化y、临时对象,调用重载赋值运算符给y赋值
	return 0;
}

77.delete、free释放空间,是让编译器明白当前指针所指向的内存区域不再使用了,可以将其用作他用。但这块内存仍然可以通过相应的指针进行访问,只是访问结果是不确定的,因为此时这块内存可能被分配出去了,也可能尚未被分配。其存储的数据可能已经被擦除,也可能未被擦除。

78.printf()函数有一个默认的 const char* const 类型参数

  _Check_return_opt_
  _CRT_STDIO_INLINE int __CRTDECL printf(
      _In_z_ _Printf_format_string_ char const* const _Format,
        ...)

因此可以直接输出字符串,而不需要加上格式控制符。
printf()函数输出存储字符串的指针或者数组,输出到字符串结束标识符 ‘\0’ 为止。因此,若待输出数据不包含 ‘\0’,则输出结果会有问题。

char* str = (char*)malloc(100);
strcpy_s(str, sizeof("123456"), "123456");
printf("%s",str);
printf(str);
free(str);

79.浮点型数据强制转换为整型数据
向下取整,而非四舍五入。

int ac = (int)2.5;
cout << ac << " ";
ac = (int)2.1;
cout << ac << " ";
ac = (int)2.9;
cout << ac << " ";
// 输出结果均为 2

80.执行main()函数之前,编译器会为全局对象以及静态对象调用构造函数,之后将main()函数的参数传给main()函数。
可执行文件的执行过程:
1)通过系统调用创建一个新的进程;
2)建立可执行文件与虚拟内存之间的映射,初始化进程环境;
3)execve()调用启动代码,启动代码将调用main()函数,当启动代码将main()函数的虚拟地址传递给CPU时,CPU通过解析虚拟地址发现内存中没有main()相对应的页或者物理块,然后CPU通过进程中的页表项找到可执行文件所在的磁盘位置,将磁盘上的块拷贝到内存中(即缺页中断),之后CPU执行可执行文件。

81.整型溢出

int a=INT_MAX,b=INT_MIN;
int c=a-b;// a-b 会溢出
long long d=a-b;// a-b 仍会溢出
long long e=(long long)a-b;// a-b 不再溢出

82.只能使用指针的场景
1)一个指针所指向的对象,需要用分支语句加以确定,或者在中途需要改变所指的对象。
2)有时会用空指针表达特定的含义,例如malloc函数的返回值。

存在函数引用

void fun(int a)
{
    cout << a << endl;
}
int main()
{
    int c = 9;
    auto p = c;// int
    auto& q = c;// int&
    
    void (*pf)(int) = &fun; 
    void(*pff)(int)=fun;
    void(&rf)(int) = fun; //函数引用
    //void(&rf)(int) = &fun; //编译不通过
    pf(4);
    rf(5);
    
    auto r = fun; //推导为函数指针
    auto& t = fun; //推导为函数引用

	int a[] = { 1,2,3 };
    auto& x = a;// int(&x)[3]
    auto y=a;// int*
    x[0];
    y[0];
    return 0;
}

83.形参可以带有默认值!! 若函数有声明,也有定义,则此函数不能有默认参数。
若函数有多个形参,则最后一个有默认值的参数必须是最右边的形参。
即 1)若当前参数有默认值,则当前参数右侧的所有参数都必须有默认值。2)若某两个形参有默认值,则这两个形参之间的所有参数都必须有默认值。

全局变量、全局常量、带有返回值的函数都可以作为默认参数,默认参数的地址在编译期便被确定了。但是局部变量、局部常量不能作为默认参数。因为局部变量、局部常量被分配在栈空间,而仅当线程被创建时,系统才给线程分配栈空间内存,所以当程序开始运行时,局部变量、局部常量的内存位置才能被确定。
CPU在程序运行时,依次序执行各条指令。

class A
{
public:
	A(int d = 0) :a(d) {} //形参可以带有默认值!!
	int a;
};
int main()
{
	int po = 1;
	switch (po)
	{
	case 1:
		cout << "1 ";
	case 2:
		cout << "2\n";
		break;
	default:
		cout << "3\n";
	}
	
	char str[] = "123456";
	cout << sizeof(str) << endl;

	cout << 031 << endl;
    return 0;
}
//  1 2
// 7
// 25

二进制、八进制、十六进制、十进制表示法。

int a = 100;
printf("%o\n", a);	//八进制 
printf("%d\n", a);	//十进制 
printf("%x\n", a);   //十六进制 

int aa = 0b100;  //二进制
// int aa=0B100;
int b = 0100;   //八进制 
int c = 100;    //十进制 
int d = 0x100;  //十六进制 
// int d=0X100;

cout << aa << " " << b << " " << c << " " << d << endl;

在这里插入图片描述
84.链接器、符号表
extern 可以认为是链接阶段的关键字,因为 extern 告诉编译器存在着一个变量或者一个函数,如果在当前编译语句的前面中没有找到相应的变量或者函数,那么会在其它文件中找到定义。

编译器把一个cpp编译为目标文件的时候,除了要在目标文件里写入cpp里包含的数据和代码,还要至少提供3个表:未解决符号表,导出符号表和地址重定向表。
未解决符号表提供了所有在该编译单元里引用但是定义并不在本编译单元里的符号及其出现的地址。
导出符号表提供了本编译单元具有定义,并且愿意提供给其他编译单元使用的符号及其地址。
地址重定位表提供了本编译单元所有对自身地址的引用的记录(仅对于静态链接)。

链接器进行链接的时候,首先确定各个目标文件在最终可执行文件里的位置。然后访问所有目标文件的地址重定向表,对其中记录的地址进行重定向(即加上该编译单元实际在可执行文件里的起始地址)。然后遍历所有目标文件的未解决符号表,并且在所有的导出符号表里查找匹配的符号,并在未解决符号表中所记录的位置上填写实际的地址(也要加上拥有该符号定义的编译单元实际在可执行文件里的起始地址)。最后把所有的目标文件的内容写在各自的位置上,再进行一些别的工作,形成最终的可执行文件。

extern:用于向编译器说明,这个符号在别的编译单元里定义,也就是要把这个符号放到未解决符号表里去。(外部链接)
static:如果该关键字位于全局函数或者变量的声明的前面,表明该编译单元不导出这个函数/变量的符号。因此无法在别的编译单元里使用。(内部链接)。如果是static局部变量,则该变量的存储方式和全局变量一样,但是仍然不导出符号。

默认链接属性:对于函数和变量,默认是外部链接,对于const变量,默认是内部链接。(可以通过添加extern和static改变链接属性)
外部链接的利弊:外部链接的符号,可以在整个程序范围内使用(因为导出了符号)。但是同时要求其他的编译单元不能导出相同的符号(不然就是duplicated external symbols) .
内部链接的利弊:内部链接的符号,不能在别的编译单元内使用。但是不同的编译单元可以拥有同样名称的内部链接符号。

85.C中调用C++的函数(link)
普通函数、成员函数、重载的函数。

86.CAS实现无锁队列(link)(link)(link)

CAS是原子操作,Compare And Swap,几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是 CMPXCHG 汇编指令。

CAS比较的是指针的值,不是地址!

入队:
1)首先判断队尾元素指针 p 的 next 指针是否指向 nullptr,若是,则使 p->next 指向新加入的节点(为了阻塞其他线程)。若不是,则当前线程一直被阻塞,直到符合条件。
2)检查队尾指针是否是p,若是,则使其指向新加入的节点。因为此时p一定指向队尾元素(此时尚未更新队尾元素),所以当前操作的作用是更新队尾元素,队尾元素的 next 指针指向 nullptr.

出队:
此处的头节点并不是队列的第一个有效的节点,而是哨兵节点,用于减少边界条件。
1)使用CAS判断保存的头节点的值是否与队列头节点的值相同(不同的话说明被其他线程修改了)。其他线程修改头节点之后,会更新头节点指针。这样,所有线程保存的头节点指针对应的值也会被更新。
2)线程通过CAS验证后,会取出此时头节点的下一个节点对应的值,将其作为返回值,并将头节点更新为头节点的下一个节点,即当前队列的第一个有效节点。
3)将哨兵节点更新为队列之前的第一个有效节点后,释放原先的哨兵节点。

原子变量:在程序中修改一个变量的值,需要进行三步操作:读取、修改、更新。若这三步之间不可被打断,则此变量为原子变量。
原子操作:指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,在执行完毕之前不会被任何其它任务或事件中断。

想要防止读写冲突,也可以提供两个原子变量A、B。
A指示是否有写操作,B指示当前有多少正在进行的读操作,一旦写操作来临,先置位A,然后判断B的值,如B为0则开始写入操作,非零则阻塞直到B为0。对于读操作,则先判断A的值,若检验通过,再更新B的值,之后进行读操作。

87.__stdcall,__cdecl,__pascal,__fastcall的区别
对于可变参数的函数,只能使用 _cdcel函数调用约定。(17、61、63、88)
因为对于_cdcel约定,由调用者清除栈中保存的参数。而对于_stdcall约定,由被调用函数在函数返回后,清除栈中保存的参数。即 ret 与 ret n,n为参数占用的字节数(link)。
汇编指令是在编译期生成的,而对于可变参数的函数,在运行时,函数才能知道外部传入的参数的个数,即在运行时才能知道 n 的大小,因此_stdcall约定不能用于可变参数函数。

88.学习其他编程语言的方法
1)了解语言的设计思想;
2)如果设计思想相似,那么语法特性就会有一定的相似之处。对于熟悉的语言特性,可以略看。将学习重点放到新的语言特性之上。在学习的过程中,可以尝试实现学习资料上的用例。
语言特性:变量定义、算术运算、循环、函数等。
学习资料:书籍、标准文档、视频等。
3)在对于语言特性有初步了解之后,开始编程实践。例如可以尝试重新实现之前编写的一些功能,或者在做算法题时,试着用相应语言来编写程序。在实践之中,逐步提升自己对相应语言的熟悉度。
4)学习一些进阶知识,例如类似于《C陷阱与缺陷》、《Effective C++》、《More Effective C++》等的知识。
5)学习语言“背后”的知识,例如语言特性的实现原理等。

89.模板类型推导(link)(link)

template<typename T>
void f(ParamType param);

1)当ParamType 既不是指针也不是引用时,采用值传递方式,忽略所传递的参数的引用属性、const属性、volatile属性。
2)当ParamType 是指针或引用,但不是通用引用时,忽略所传递的参数的引用属性,之后先确定ParamType的类型,再推导T的类型。
3)当ParamType 是通用引用时,若所传递的参数是左值,则T和ParamType的类型都被推导为左值引用。若所传递的参数是右值,则ParamType的类型被推导为右值引用,T的类型被推导为相对应的非引用类型。
4)通过非引用传递数组参数、函数参数时,ParamType类型被推导为指针类型,通过引用方式传递时,ParamType类型被推导为引用类型,即数组引用、函数引用(132)。

90.三目运算符不能包含返回语句。

int a=1,b=0;
a>b?return a:++a;// 不能通过编译

非零整数可以表示条件为真,但是 true==1,不等于其他非零整数。
因此,对于返回值为整型的用于判断的函数,不可直接写成if(judge()==true),可以写为if(judge())

 cout << (int)true << " " << (int)false << endl; // 1 0
 if (1==true)
      cout << "1";
 else
      cout << "2"; // if(2==true)

要注意运算符优先级。

char ch = 'b';
int c = INT_MAX - 5;
int a = c + ch - 'a';
int b = c + (ch - 'a');
cout << a << " " << b << endl;
// 剑指 Offer 67. 把字符串转换成整数

91.引用的设计目的
1)使用引用比使用指针更安全,因为指针可能是野指针、悬空指针、空指针,也有可能无意间更改了指针所指向的对象。
2)便于运算符重载。例如A operator +(const A *a, const A *b);使用的时候,需要使用&a + &b,代码不够简洁。
3)解除引用运算符可以被重载。

const引用作为形参的好处:
1)const 左值引用可以接收const、非const 左值和右值引用。
非const左值引用只能接收非常量同类型左值。
2)不允许函数修改对应的参数。
3)传递效率更高,因为不需要调用拷贝构造函数来进行额外构造。
在这里插入图片描述
迭代器的设计目的:
1)迭代器封装了指针,模拟了指针的一些功能,并重载了指针的一些操作符。迭代器可以根据不同类型的容器来实现不同的操作。迭代器返回的是对象引用而不是对象的值。
2)使用迭代器,用户可以在不了解容器内部结构的前提下,对容器进行相应的操作。

92.new/delete与malloc/free
前者是为了便于面向对象而设计的,基于后者而实现。对于自定义类型,使用new/delete时会调用相应的构造函数和析构函数。
区别
delete [] 与 delete
new/delete与malloc/free混合使用

int* p = (int*)malloc(sizeof(int));
*p = 5;
int a=5;
//p=&a; 
free(p);

93.处理中文字符(link)

#include<locale>
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string str;
	cin >> str;//以空格为结束符
	getline(cin, str);//忽略空格
	cout << str;
	return 0;

	locale china("chs");//use china character
	wcin.imbue(china);//use locale object
	wcout.imbue(china);
	wstring s;
	wchar_t wc = L'。';//wide character,wide string may be L"宽字符"
	while (getline(wcin, s, wc))
	{
		wcout << s << endl;
	}
}

cin读取输入数据
另起一行,按下 CTRL + Z,并回车,即表明数据输入完成。

int a, b;
vector<pair<int, int> > vec;
char ch;
while (true)
{
   ch = cin.get();
   if (ch != ' ')
      continue;
   else
   {
       cin >> a >> b;
       vec.push_back({ a,b });

       if (!(cin >> ch))
          break;
    }
}

在这里插入图片描述
94. 换行符 (std::endl, ‘\n’) 会刷新缓冲区。
echo_read_write_client.cpp

int strLen=read(clntSock,buf,BUF_SIZE);
if(strLen==0)
	return;
buf[strLen]='\0';
// std::endl,'\n' will flush the buffer
//std::cout<<buf<<std::endl;   // 1
//std::cout<<buf;			  // 2
printf("Message from server: %s\n",buf);

// 1: 直接输出信息。
// 2: 待缓冲区满,或者关闭客户端时,再一次性输出全部信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值