1. C++中使用空对象指针调用成员函数
class TestCls
{
public:
static void Test_Fun1(){ cout<<"Test_Fun1"<<endl; }
void Test_Fun2(){ cout<<"Test_Fun2"<<endl; }
void Test_Fun3(){ cout<<m_num<<endl; }
virtual void Test_Fun4{ cout<<"Test_Fun4"<<endl; }
public:
int m_num;
};
int main()
{
TestCls* pTestCls = NULL;
pTestCls->Test_Fun1(); //OK, print Test_Fun1
pTestCls->Test_Fun2(); //OK, print Test_Fun2
pTestCls->Test_Fun3(); //Error
pTestCls->Test_Fun4(); //Error
}
空指针对Test_Fun1和 Test_Fun2的调用正常,对Test_Fun3和Test_Fun4的调用会出错。下面具体分析一下原因:
类的成员函数并不与具体对象绑定,所有的对象共用同一份成员函数体,当程序被编译后,成员函数的地址即已确定,这份共有的成员函数体之所以能够把不同对象的数据区分开来,靠的是隐式传递给成员函数的this指针,成员函数中对成员变量的访问都是转化成"this->数据成员"的方式。因此,从这一角度说,成员函数与普通函数一样,只是多了一个隐式参数,即指向对象的this指针。而类的静态成员函数只能访问静态成员变量,不能访问非静态成员变量,所以静态成员函数不需要指向对象的this指针作为隐式参数。
有了上面的分析,就可以解释为什么空对象指针对Test_Fun1, Test_Fun2的调用成功,对Test_Fun3 的调用不成功:
Test_Fun1是静态成员函数,不需要this指针,所以即使pTestCls是空指针,也不影响对Test_Fun1的正常调用。
Test_Fun2虽然需要传递隐式指针,但是函数体中并没有使用到这个隐式指针,也就是说没有通过这个隐式指针去使用非静态的成员变量,所以pTestCls为空也不影响对Test_Fun2的正常调用。
Test_Fun3就不一样了,因为函数中使用到了非静态的成员变量,对num的调用被转化成this->num,也就是pTestCls->num,而pTestCls是空指针,因此pTestCls->num非法,对Test_Fun3的调用出错。
Test_Fun4中并没有使用非静态成员变量,为什么调用也会出错呢,原因在于Test_Fun4是虚函数有虚函数的类会有一个成员变量,即虚表指针,当调用虚函数时,会使用虚表指针,对虚表指针的使用也是通过隐式指针使用的,因此对Test_Fun4的调用也就会出错了。
因为对于非虚成员函数,C++这门语言是静态绑定的。这也是C++语言和其它语言Java, Python的一个显著区别。但是C++中为什么要进行这样的定义呢?答案是处于性能的考虑。为了节约内存和提高调用效率,一般类成员的存储分成两块,一块是单个instance所有,比如非静态成员变量,另一块是所有instances共享的,比如函数代码。这样的布局是对于性能有好处的,代码只要load一次,减少了cache占用和miss。如果你的函数不引用任何instance独有的内存部分,nullptr并无问题,因为不会使用this,只会使用类instance共享的部分,这部分始终存在,即使你没有任何类实例
c++空指针调用类成员函数
https://www.zhihu.com/question/23260677
C++ this 指针
2. C语言在子函数中调用malloc申请内存的方法
- 1.函数返回
#include <stdio.h>
#include <malloc.h>
#include <string.h>
char* test()
{
char *p;
p = (char*)malloc(10 * sizeof(char));
strcpy(p, "123456789" );
return p;
}
void main()
{
char *str = NULL ;
str = test();
printf("%s\n", str);
free(str);
}
- 二级指针
#include <stdio.h>
#include <malloc.h>
#include <string.h>
void test(char **p)
{
*p = (char*)malloc(10 * sizeof(char));
strcpy(*p, "123456789" );
}
void main()
{
char *str = NULL ;
test(&str);
printf("%s\n", str);
free(str);
}
3. 虚函数可以私有吗
私有虚拟函数可以在派生类中被重写。派生类的方法不能从基类调用虚拟函数,但它们可以为它们提供自己的实现。
4. 设计不能被继承的类
1. C++ 11 中引入了final 关键字
1.禁用继承
C++11中允许将类标记为final,方法时直接在类名称后面使用关键字final,如此,意味着继承该类会导致编译错误。
实例如下:
class Super final
{
//......
};
2.禁用重写
C++中还允许将方法标记为fianal,这意味着无法再子类中重写该方法。这时final关键字至于方法参数列表后面,如下
class Super
{
public:
Supe();
virtual void SomeMethod() final;
};
https://blog.csdn.net/mayue_web/article/details/88406527
2. 不用final关键字设计不能被继承的类
template <typename T>
class MakeFinal
{
friend T;
private :
MakeFinal() {}
~MakeFinal() {}
};
class FinalClass : virtual public MakeFinal<FinalClass>
{
public :
FinalClass2() {}
~FinalClass2() {}
};
class Try : public FinalClass
{
public :
Try() {}
~Try() {}
};
Try temp; 创建失败
由于类FinalClass是从类MakeFinal<FinalClass>虚继承过来的,在调用Try的构造函数的时候,会直接跳过FinalClass而直接调用MakeFinal<FinalClass>的构造函数。非常遗憾的是,Try不是MakeFinal<FinalClass>的友元,因此不能调用其私有的构造函数。
基于上面的分析,试图从FinalClass继承的类,一旦实例化,都会导致编译错误,因此是FinalClass不能被继承。这就满足了设计要求。
https://www.jianshu.com/p/b8a5a8c2cec7
5. sizeof() 和 strlen()
char *c="abcdef";
char d[]="abcdef";
char e[]={'a','b','c','d','e','f'};
printf("%d%d/n",sizeof(c),strlen(c));
printf("%d%d/n",sizeof(d),strlen(d));
printf("%d%d/n",sizeof(e),strlen(e));
// 输出的结果是:
4 6
7 6
6 14
第一行定义c为一个字符指针变量,指向常量字符串,c里面存放的是字符串的首地址。
第二行定义d为一个字符数组,以字符串的形式给这个字符数组赋值。
第三行定义的也是个字符数组,以单个元素的形式赋值。
当以字符串赋值时,"abcdef",结尾自动加一个"/0".
strlen(c)遇到/0就会结束,求的是字符串的长度,为6.
sizeof(c)求的是类型空间大小,在前面说过,指针型所点的空间大小是4个字节,系统地址总线长度为32位时。
strlen(d)也是一样,字符串赋值,自动添加/0,求字符串的长度当然是6.
sizeof(d)是求这个数组所占空间的大小,即数组所占内存空间的字节数,应该为7.
sizeof(e), 数组e以单个元素赋值,没有/0结束符,所以所占空间的大小为6个字节。
strlen(e),去找/0结尾的字符串的长度,由于找不到/0,所以返回的值是一个不确定的值。
6. memcmp比较结构体
https://www.twblogs.net/a/5c8ba0ccbd9eee35fc14de5b/?lang=zh-cn
7. 右值引用
右值引用是C++11中新增加的一个很重要的特性,他主是要用来解决C++98/03中遇到的两个问题,第一个问题就是临时对象非必要的昂贵的拷贝操作,第二个问题是在模板函数中如何按照参数的实际类型进行转发。通过引入右值引用,很好的解决了这两个问题,改进了程序性能
在C++11中所有的值必属于左值、将亡值、纯右值三者之一。比如,非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等都是纯右值。而将亡值是C++11新增的、与右值引用相关的表达式,比如,将要被移动的对象、T&&函数返回值、std::move返回值和转换为T&&的类型的转换函数的返回值等
C++11正是通过引入右值引用来优化性能,具体来说是通过移动语义来避免无谓拷贝的问题,通过move语义来将临时生成的左值中的资源无代价的转移到另外一个对象中去,通过完美转发来解决不能按照参数实际类型来转发的问题
https://www.cnblogs.com/qicosmos/p/4283455.html
8 automic和 memery order
https://blog.csdn.net/WizardtoH/article/details/81111549
http://senlinzhan.github.io/2017/12/04/cpp-memory-order/
https://www.cnblogs.com/haippy/p/3306625.html
9. 四种cast
static_cast、dynamic_cast、reinterpret_cast、和const_cast
- static_cast :
static_cast 不提供运行时候的动态类型检查。- 用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
- 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
- 把空指针转换成目标类型的空指针。
- 把任何类型的表达式转换成void类型。
- dynamic_cast
可以安全的将父类转化为子类,子类转化为父类都是安全的。所以你可以用于安全的将基类转化为继承类,而且可以知道是否成功,如果强制转换的是指针类型,失败会返回NULL指针,如果强制转化的是引用类型,失败会抛出异常。dynamic_cast 转换符只能用于含有虚函数的类 - reinterpret_cast
重新解释(无理)转换。即要求编译器将两种无关联的类型作转换。它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。 - const_cast
该运算符用来修改类型的const或volatile属性。常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。相当于C语言中的强制类型转换的替代品,但是不可以将两个无关的类型互相转化。
9. 智能指针
auto_ptr支持拷贝构造与赋值操作,但unique_ptr不直接支持
auto_ptr通过拷贝构造或者operator=赋值后,对象所有权转移到新的auto_ptr中去了,原来的auto_ptr对象就不再有效(置为空指针),这点不符合人的直觉。unique_ptr则直接禁止了拷贝构造与赋值操作。std::move让调用者明确知道拷贝构造、赋值后会导致之前的unique_ptr失效
weak_ptr并不改变其所共享的shared_ptr实例的引用计数,那就可能存在weak_ptr指向的对象被释放掉这种情况。这时,就不能使用weak_ptr直接访问对象。那么如何判断weak_ptr指向对象是否存在呢?C++中提供了lock函数来实现该功能。如果对象存在,lock()函数返回一个指向共享对象的shared_ptr(引用计数会增1),否则返回一个空shared_ptr。weak_ptr还提供了expired()函数来判断所指对象是否已经被销毁。
由于weak_ptr并没有重载operator ->和operator *操作符,因此不可直接通过weak_ptr使用对象,同时也没有提供get函数直接获取裸指针。典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。
weak_ptr弱引用智能指针详解
#include <iostream>
#include <memory>
template<typename T>
class SmartPointer {
private:
T* _ptr;
size_t* _count;
public:
SmartPointer(T* ptr = nullptr) :
_ptr(ptr) {
if (_ptr) {
_count = new size_t(1);
} else {
_count = new size_t(0);
}
}
SmartPointer(const SmartPointer& ptr) {
if (this != &ptr) {
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
}
}
SmartPointer& operator=(const SmartPointer& ptr) {
if (this->_ptr == ptr._ptr) {
return *this;
}
if (this->_ptr) {
(*this->_count)--;
if (*this->_count == 0) {
delete this->_ptr;
delete this->_count;
}
}
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
return *this;
}
T& operator*() {
assert(this->_ptr == nullptr);
return *(this->_ptr);
}
T* operator->() {
assert(this->_ptr == nullptr);
return this->_ptr;
}
~SmartPointer() {
(*this->_count)--;
if (*this->_count == 0) {
delete this->_ptr;
delete this->_count;
}
}
size_t use_count(){
return *this->_count;
}
};
int main() {
{
SmartPointer<int> sp(new int(10));
SmartPointer<int> sp2(sp);
SmartPointer<int> sp3(new int(20));
sp2 = sp3;
std::cout << sp.use_count() << std::endl;
std::cout << sp3.use_count() << std::endl;
}
//delete operator
}
10. new 和malloc的区别
1、malloc与free是c++/c语言的标准函数,new/delete是C++的运算符。
2、他们都可用于申请动态内存和释放内存。new/delete比malloc/free更加智能,其实底层也是执行的malloc/free。为啥说new/delete更加的智能?因为new和delete在对象创建的时候自动执行构造函数,对象消亡之前会自动执行析构函数。
既然new/delete的功能完全覆盖了malloc和free,为什么C++中不把malloc/free淘汰出局呢?因为c++程序经常要调用c函数,而c程序只能用malloc/free管理动态内存。
3、new返回指定类型的指针,并且可以自动计算出所需要的大小。如 :
int p; p = new int; //返回类型为int类型,大小为sizeof(int);
int *pa; pa = new int[50];//返回类型为int ,大小为sizeof(int) * 100;
malloc必须用户指定大小,并且默然返回类型为void,必须强行转换为实际类型的指针。
11. C++动态内存分配
C++的内存分配(动态)主要有两种方式operator new 和 new operator,其中new operator是C++里面的关键字,operator new是C++里面的一个操作符,可以重载, 在C++中 A* = new A(value); 其实分为了两步,第一步会调用::operator new(size_t)内存的分配,然后调用new (p) A(value) 来调用A的构造函数, C++ delete与new相同首先调用析构函数,其次再调用::operator delete(void*) 来释放内存,其实在C++中::operator new 和::operator delete都是调用了C语言都malloc函数和free函数来进行真正的内存操作,C语言的malloc(linux)最终也会走到系统调用层(brk,sbrk),然后brk,sbrk最终会调用到kmalloc,vmalloc等....
127 .C++ cin的使用
注意大部分情况下,cin缓冲区中的换行符都不会被清楚,下次输入的时候记得处理
cin.get读取一行时,遇到换行符(自定义结束符)时结束读取,但是不对换行符(自定义结束符)进行处理,换行符(自定义结束符)仍然残留在输入缓冲区。
getline读取一行字符时,默认遇到’\n’(自定义结束符)时终止,并且将’\n’(自定义结束符)直接从输入缓冲区中删除掉,不会影响下面的输入处理。
cin.get(str,size);读取一行时,只能将字符串读入C风格的字符串中,即char*,但是C++的getline函数还可以将字符串读入C++风格的字符串中,即string类型
可以使用 cin.get(str,5).get()的方式来清楚末尾的换行符;用cin.get() 读取到换行符
也可以使用cin.ignore()的方式清楚换行符
cin.getline(数组名,长度,结束符) 大体与 cin.get(数组名,长度,结束符)类似
cin.getline()的原型istream& getline(char* s, streamsize count, char delim);
getline()的原型是istream& getline ( istream &is , string &str , char delim );
其中 istream &is 表示一个输入流,譬如cin;
string&str表示把从输入流读入的字符串存放在这个字符串中(可以自己随便命名,str什么的都可以);
char delim表示遇到这个字符停止读入,在不设置的情况下系统默认该字符为'\n',也就是回车换行符(遇到回车停止读入)。
区别
1.cin.get()当输入的字符串超过规定长度时,不会引起cin函数的错误,后面的cin操作会继续执行,只是直接从缓冲区中取数据。
但是cin.getline()当输入超过规定长度时,会引起cin函数的错误,后面的cin操作将不再执行。
当输入的字符数大于count时,则get函数只读取count-1个字符,而其余的字符仍然保存在缓冲区中,还可再对其进行读取;
但是函数getline则不然,getline()会设置失效位(faibit),并且关闭后面的输入,这个时候再用ch=cin.get()是读取不到留在输入队列中的字符的。
可以用下面的命令来恢复输入:
cin.clear();//因为clear()会重置失效位,打开输入。这个时候ch=cin.get();就可以读取留在输入队列中的字符。
2.cin.get读取一行时,遇到换行符(自定义结束符)时结束读取,但是不对换行符(自定义结束符)进行处理,换行符(自定义结束符)仍然残留在输入缓冲区。
getline读取一行字符时,默认遇到’\n’(自定义结束符)时终止,并且将’\n’(自定义结束符)直接从输入缓冲区中删除掉,不会影响下面的输入处理。
两者都会在读取的字符串后面自动加上'\0'
3.cin.get(str,size);读取一行时,只能将字符串读入C风格的字符串中,即char*,但是C++的getline函数还可以将字符串读入C++风格的字符串中,即string类型。(string test; getline(cin,test);)
鉴于getline较cin.get()的优点,建议使用getline进行行的读取。
输入缓冲区清除方法
通常大家会用sync()函数来清除输入缓冲区的内容。个人感觉还是用ignore更好。
cin.clear()是用来更改cin的状态标示符的,cin在接收到错误的输入的时候,会设置状态位good。如果good位不为1,则cin不接受输入,直接跳过。如果下次输入前状态位没有改变那么即使清除了缓冲区数据流也无法输入。所以清除缓冲区之前必须要cin.clear()。
sync()的作用就是清除输入缓冲区。成功时返回0,失败时badbit会置位,函数返回-1.
另外,对于绑定了输出的输入流,调用sync(),还会刷新输出缓冲区。
但由于程序运行时并不总是知道外部输入的进度,很难控制是不是全部清除输入缓冲区的内容。通常我们有可能只是希望放弃输入缓冲区中的一部分,而不是全部。
比如清除掉当前行、或者清除掉行尾的换行符等等。但要是缓冲区中已经有了下一行的内容,这部分可能是我们想保留的。这个时候最好不要用sync()。可以考虑用ignore函数代替。
cin.ignore(numeric_limits<std::streamsize>::max(),’\n’);//清除输入缓冲区的当前行
cin.ignore(numeric_limits<std::streamsize>::max()); //清除输入缓冲区里所有内容
cin.ignore()//清除一个字符
可采用cin.get(str,5).get();//清除后面的换行符
不要被长长的名字吓倒,numeric_limits<std::streamsize>::max()不过是climits头文件定义的流使用的最大值,你也可以用一个足够大的整数代替它。
https://blog.csdn.net/selina8921/article/details/79067941
12 C/C++的内存分配
栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
堆区(heap):一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
全局区(静态区static):存放全局变量、静态数据、常量。程序结束后由系统释放。全局区分为已初始化全局区(data)和未初始化全局区(bss)。
常量区(文字常量区):存放常量字符串,程序结束后有系统释放。
代码区:存放函数体(类成员函数和全局区)的二进制代码。
C的内存分配:
1、BSS段: 用来存放程序中未初始化的全局变量。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
2、数据段:用来存放程序中已初始化的全局变量。数据段属于静态内存分配。
3、代码段:用来存放程序执行代码。
4、堆:堆是用于存放进程运行中被动态分配的内存段,当进程调用malloc/free等函数分配内存时,新分配的内存就被动态添加到堆上。
5、栈:栈又称堆栈, 存放程序的局部变量。除此以外,在函数被调用时,栈用来传递参数和返回值。
C++的内存分配:
1、栈:内存由编译器在需要时自动分配和释放。通常用来存储局部变量和函数参数。
2、堆:内存使用new进行分配使用delete或delete[]释放。
3、自由存储区:使用malloc进行分配,使用free进行回收。和堆类似。
4、全局/静态存储区:全局变量和静态变量被分配到同一块内存中,C语言中区分初始化和未初始化的,C++中不再区分了。
5、常量存储区:存储常量,不允许被修改。
13 C++动态内存分配
C++的内存分配(动态)主要有两种方式operator new 和 new operator,其中new operator是C++里面的关键字,operator new是C++里面的一个操作符,可以重载, 在C++中 A* = new A(value); 其实分为了两步,第一步会调用::operator new(size_t)内存的分配,然后调用new (p) A(value) 来调用A的构造函数, C++ delete与new相同首先调用析构函数,其次再调用::operator delete(void*) 来释放内存,其实在C++中::operator new 和::operator delete都是调用了C语言都malloc函数和free函数来进行真正的内存操作,C语言的malloc(linux)最终也会走到系统调用层(brk,sbrk),然后brk,sbrk最终会调用到kmalloc,vmalloc等....
https://blog.csdn.net/cherrydreamsover/article/details/81627855
14. C++ 常见的内存问题
memory overrun:写内存越界
double free:同一块内存释放两次
use after free:内存释放后使用
wild free:释放内存的参数为非法值
access uninitialized memory:访问未初始化内存
read invalid memory:读取非法内存,本质上也属于内存越界
memory leak:内存泄露
use after return:caller访问一个指针,该指针指向callee的栈内内存
stack overflow:栈溢出
valgrind 检查原理
15. 深浅拷贝
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
16. C++ 异常机制
17 placement new
placement new的好处:
1)在已分配好的内存上进行对象的构建,构建速度快。
2)已分配好的内存可以反复利用,有效的避免内存碎片问题。
如果使用MyClass * pClass=new MyClass;会经历2步:
1:用operator new申请内存
2:在申请到的内存上构建对象,使用MyClass * pClass=new(buf) MyClass;
18 空间配置器allocator的实现原理
allocator需要实现如下功能:
- 将底层的alloc(内存分配器,只分配以字节为单位的原始内存,并构建了自己的内存池)封装起来,只保留以对象大小为单位的内存分配
- 对alloc分配出来的地址空间进行placement new 构造
- 对自身构造出来的内容进行对应的析构(需要和构造进行配对,比如用 allocator构造的对象一般只允许被allocator所析构)
allocator 就只做这几件事情:分配内存、构造对象、析构对象、释放内存
allocator基本上只实现了以下几个方法:
allocate() : 内存空间分配(按对象的大小为单位从alloc中获取)
construct() : placement new 指定地址构造对象
destroy() : operator delete 指定地址析构对象
deallocate() : 内存空间的回收(释放给alloc进行管理)
https://blog.csdn.net/codedoctor/article/details/78110973
19 产生core dump的原因
- 内存访问越界
a) 由于使用错误的下标,导致数组访问越界。
b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符。
c) 使用strcpy, strcat, sprintf, strcmp,strcasecmp等字符串操作函数,将目标字符串读/写爆。 - 多线程程序使用了线程不安全的函数。
- 多线程读写的数据未加锁保护。
- 对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成coredump
- 非法指针
a) 使用空指针
b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump。 - 堆栈溢出
不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。