CPP常识

CPP常识

const变量是否可以修改
const局部变量定义在text段,而const全局变量定义在rodata段。当局部变量在定以后会压入栈区,此时可以在栈区进行修改,而全局区则完全不能修改

在一台内存为2G的机器上,malloc(20G)会怎么样?
malloc返回null,new返回bad_alloc
c++写时复制:
class buf{
char* s1
}
当对buf进行拷贝复制时,就有深拷贝浅拷贝之分,如果选用深拷贝,就要额外开辟一片堆空间进行复制,但此时并不需要对s1指向的空间进行修改,浪费了系统操作时间,但不使用深拷贝,析构的时候又会产生空间重复释放的问题
所以,使用浅拷贝的同时,附加一个计数器,用来计数资源被使用了多少次,直到归零才将空间释放
返回对象
会拷贝复制一份再返回
左值与右值
A& a 左值引用 A&& a 右值引用
一般来说,能取地址的是左值,不能取地址的就是右值
int i = 0
i是左值,0是右值
int i = getint()
i是左值,getint()的返回值是右值,getint返回值在给int赋值后就销毁了

如果使用右值引用:
int&& i = getint(),那么getint的返回值就不会销毁,换而言之i指向的返回值原来的空间

移动构造函数与拷贝构造函数对比
移动构造函数参数为右值引用,并且是移动数据;拷贝构造函数为左值引用,拷贝一份新数据.
当没有移动构造函数时,假如函数返回值是类,则函数返回对象时会调用拷贝构造
拷贝构造函数
拷贝构造函数只有一个参数
class car{
car(const car& a);
}
1 car a1; car a2(a1)//调用了拷贝构造
2 a2 = a1等价与a2(a1)//但当a2,a1都定义过后,赋值操作不会调用拷贝构造
3 void func(car a); car a1 ; func(a1);//传形参时调用拷贝
C++的空类:
空类的函数:
缺省构造函数。 空类的大小为1字节
缺省拷贝构造函数。 为了确定地址,至少一字节
缺省析构函数。
缺省赋值运算符。
缺省取址运算符。
缺省取址运算符 const。
前四个客观存在,后两个只在使用的时候编译器才会去定义他们
struct与class的区别
class有默认的无参构造无参析构函数,struct只能自定义有参构造函数,没有析构函数
class默认的数据访问控制是私有,struct默认的是公有
class必须使用new初始化,struct则没有必须
malloc和new区别
malloc(int size)本质上是从堆空间获取一定大小的空间,返回void指针指向该空间,需要成对使用free释放申请的空间,不然会造成内存泄漏,重复释放也会造成报错;申请是否成功需要检查
new new本质上是调用了malloc进行空间分配,但是分配对象空间时,还会调用对象的构造函数,并且可以直接计算出对象占用的空间.此外,new申请空间不用检查是否成功,如若失败,会抛出bad_alloc的报错.
内存泄漏的几种情况
1 构造函数中分配了内存,析构函数中没有对应的释放,造成一部份内从永远无法释放,造成内存泄露
2 释放对象数组的时候没有加上[],使得只释放了1个
3 没有将基类析构函数设置为虚函数时,如果用基类指向子类,那么子类的析构函数不会执行,子类资源无法正确释放.
sizeof和strlen
sizeof是c语言中的运算符,用来计算变量或数据类型在内存中占用的字节数,不受存储内容的影响,只计算空间大小
strlen是用来计算字符串大小的函数,计算字符串’\0’之前的长度
指针与引用的区别
指针是一个变量,内容是一个指向的地址;而引用只是一个对象的别名,和原来的对象一样
int a = 1
int
p = a
int&b = a
p是一个指针,p的值是a存储的地址;而b本身就是a,指向同一块地址,只是名字不同
对p做sizeof会得到p作为指针的大小,对b做sizeof,会得到a本身的大小
p++是指向发生变化,b++是a的值发生变化.
引用类似一个指向不会发生改变的指针(常量指针)
静态多态与动态多态
静态多态通过模板实现
template
class A{ func() }
class B{ func() }
class C{ func() }
fun(const T& cla )
{
cla.func()
}
这样完成了不同类的多态
动态多态则由虚函数继承完成

虚析构的必要性
calss base{}
class child{}

base* p= new child
delete p
base构造
child构造
base析构
child* p= new child
delete p
base构造
child构造
child析构
base析构
当使用基类指针生成派生对象时,会先生成派生类,然后转化为基类,因此在析构的时候只调用基类的析构,子类中的非静态变量不会释放,造成内存泄漏.因此,需要将基类的析构函数改为虚析构.即可完成子类的析构
使用子类指针生成子类对象时,调用正常,不会发生内存泄漏

volatile关键字
防止变量被编译器优化,那如何理解优化呢?
1)编译器实际上对代码有所优化,当出现之前出现过的变量,且该变量一直没有参与代码运行时,编译器不会去该变量的地址取出值,而是从类似编译器缓存的地方取出上次该变量的值,作为变量的值.如果在两次使用间该变量参与了内联汇编代码,并且值发生了改变,那么在release版本下,变量的值就会出错
比如: int i = 0; /内联汇编使i所在的内存存储的值+10/; cout<<i
那么在release版本下,i的值还会是0
所以需要加上volatile,告知编译器该变量的值在不停改变,需要每次都从内存中取
2)当处于多线程环境下,volatile可以避免编译器将变量存入cpu寄存器中.
如果A线程与B线程都在对变量i进行操作,但A线程中编译器把变量存入了寄存器中,那么两个线程一个对寄存器中的变量进行操作,一个对内存中的变量进行操作,会发生逻辑错误

四种类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast
static_cast:
对应的是强制类型转换,static_cast<new_type>(变量)
例如:
char c = ‘c’;
int b = static_cast©;
char* c = new char
void* b = static_cast<void*>©;
但是 static_cast无法去掉const
const char c = ‘c’
char = static_cast©//错的

std::move函数

int main()
{
std::string str = “Hello”;
std::vectorstd::string v;
//调用常规的拷贝构造函数,新建字符数组,拷贝数据
v.push_back(str);
std::cout << “After copy, str is “” << str << “”\n”;
//调用移动构造函数,掏空str,掏空后,最好不要使用str
v.push_back(std::move(str));
std::cout << “After move, str is “” << str << “”\n”;

shared_ptr相互引用造成的内存泄漏问题
定义A类B类
class A
{ shared_ptr B}
class B
{shared_ptr A}
shared_ptr sa = make_shared();
shared_ptr sb = make_shared();
sa->pb = sb;
sb->pa = sa;

因为两个类中互相有对方的智能指针,且无法加以释放,那么两个对象总有一个指针指向,两个对象就无法析构
如果将类中指针改为weak_ptr:
class A
{ weak_ptr B}
class B
{weak_ptr A}
那么在赋值之后指针计数没有增加,对象就能正常析构了

list和vector比较
list由双向链表实现,有链表的特性:1 不支持随机读取,所以取出效率极低,为O(n),
2 插入删除方便 3 不占用连续的空间,所以空间不够时不需要申请(没有不够的时候)
vector类似数组,有数组的特性:1 支持随机读取,效率为O(1)
2 插入删除不方便 3占用连续的空间,所以空间不够时会申请一块足够大的空间
需要高效的随机存取,而不在乎插入和删除的效率,使用vector;
如果需要大量的插入和删除,而不关心随机存取,则应使用list。

vector迭代器失效的几种情况
1 vector使用erase()函数
vector空间分配是连续的,也就是说,当删除任意一个迭代器后( irr.erase(iter) ),所有的iter迭代器都会失效,此时再向通过iter找到容器中的元素就会报错.但是erase函数在删除后会给一个返回值,这个返回值就是删除后应该的下一个,所以可以通过iter = irr.erase(iter) 来对迭代器进行删除.
2 map等关联容器使用erase()函数
关联容器(如map, set,multimap,multiset)底层都是通过红黑树实现的,当发生删除之后不会对其他节点发生影响.所以删除关联容器的迭代器不需要依靠返回值(返回值也是void),只需要在删除时进行自增操作即可,但是erase(iter++);实际上会报错,因为删除之后迭代器就已经失效了,没法自增,所以使用这种方法:
iter_t = iter;
iter++;
erase(iter_t);
map与unordered_map对比
map底层是树形结构,当向map中插入表项的时候,会根据operator<判断元素的大小,插入到合适的结点;而unordered_map则是会计算元素的hash值,根据hash值插入书中.因此,对map进行中序遍历时输出的值是有序的,而unordered_map则是无序的
对应的,map占用的空间大,插入耗时高,unordered_map占用空间小,插入耗时低

set与unordered_set对比
set是排序

STL容器空间配置器
待补充

scanf没法输入空格,而gets可以,所以有空格的字符串可以使用gets获取

struct对齐的大小取决于结构体内最大类型的大小;如果内部含有double,则大小一定是8的倍数,如果内部最大的是int,,则大小一定是4的倍数

使用内联函数的时候要注意:
递归函数不能定义为内联函数
内联函数一般适合于不存在while和switch等复杂的结构且只有1~5条语句的小函数上,否则编译系统将该函数视为普通函数。
内联函数只能先定义后使用,否则编译系统也会把它认为是普通函数。
对内联函数不能进行异常的接口声明。

resize和 reserve 的区别
在容器中size是当前容器内元素的数量大小,而capacity则是能容纳元素的最大数量;
reserve规定了capacity的大小,但此时容器内可能还没有对象,也就不能访问,就想一个矿泉水瓶,reserve造出了一个600ml的瓶子,但里面是空的,没法喝到水
resize则是创建出一定数量的元素实例,填满了空间;相当于造出一个300ml的水杯并灌满水.
resize既修改了size,也修改了capacity,而reserve只修改了capacity,不对内容进行填充

左值与右值
左值是在内存中存储并能被找到的值,右值则是(read value),是可以获取值,但没有明确的内存存储地址,并且没法通过寻址找到的值
左值引用用& 如 int & t1 = 1;
右值引用用&& 如 int&&t2 = 2;

dynamic_cast和static_cast的区别
两者都用于类型转换: xxx_cast(old object),可以将oldobject换成newtype
需要注意的是,当使用static_cast时:
当进行上行转换时(子类转化为基类),使用十分安全
当进行下行转换时(基类转换为子类),不会对子类进行动态检查,所以不安全
当使用dynamic_cast时:
当进行上行转换时(子类转化为基类),和static_cast一样,十分安全
当进行下行转换时(基类转换为子类),会进行动态检查,相对安全
那么什么是不安全呢?
比如父类A,子类B,B中有一个char数组
此时定义一个子类B的指针,指向一个B的对象,进行上行转换,那么是安全的.
此时定义一个父类A的指针,指向一个A的对象,进行下行转换,那么转换完成后得到的B类指针就有错误访问数组的风险,对于这个风险,static_cast会忽略,进而返回一个B类指针;而dynamic_cast则会因为这个风险而返回一个空指针.
需要注意的是,想要使用dynamic_cast必须在父类中定义虚函数,不然编译会报错.这是因为使用dynamic_cast进行的动态检查函数储存在对象的虚函数表中,必须要有虚函数表才能正常进行检查.

初始化的数据存在数据段,为初始化的数据存在bss段

weak_ptr
lock函数:
本质上是使用weak_ptr本身初始化一个shared_ptr并返回
即lock()
{
return shared_ptr(*this,false);
}

enable_shared_from_this
可以安全的生成智能指针.但如何理解安全与不安全呢?
当不使用时,设有一个初始类C
shared_ptr sp1(new c());
shared_ptr sp2(sp1.get_ptr()); //(按道理不应该随意使用get_ptr())
那么此时,sp1和sp2的引用计数都会为1,也就是两者都认为自己就是该指针的唯一拥有者.当生命结束后,两者都会释放指针指向的内存,造成两次释放的错误.
如果使用了enable_shared_from_this,同样进行上述操作
shared_ptr sp1(new c());
shared_ptr sp2(sp1.get_ptr()); //(按道理不应该随意使用get_ptr())
那么此时sp1和sp2的引用计数就都会为2,上述错误不会发生.
当存在异步操作时,一个变量的生命周期难以预估,可能在第一个函数结束之后第二个异步使用才会出现,那么为了保证变量的生命周期,就需要使用了.

socket网络通信要调用哪些api
socket/bind/listen/connect/recv/send

socket中的EAGAIN错误码:
1)当处于发送方时,说明send发出的包体过大,超过了tcp最大字节数,需要分片后重新发送
2)当处于接收方时,可能是非阻塞io中调用了阻塞操作,导致阻塞操作还没有做完就返回了,比如recv读取数据,还有数据没读完就返回了,在非阻塞socket中也可能是设定阻塞时间到了.这样的错误不影响读取操作,只需要循环读取完成就可以了.此外,在接受时发生错误EINTR也应该继续接受;如果recv返回0,则说明断开连接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值