#include
using namespace std;
class HeapOnly
{
public:
HeapOnly()
{
_str = new char[10];
}
~HeapOnly() = delete;
void Destroy()
{
delete[] _str;
operator delete(this);
}
private:
char* _str;
//...
};
int main()
{
HeapOnly* ptr = new HeapOnly;
ptr->Destroy();
return 0;
}
#include
#include
using namespace std;
class HeapOnly
{
public:
/*static void Delete(HeapOnly* p)
{
delete p;
}*/
void Delete()
{
delete this;
}
private:
// 析构函数私有
~HeapOnly()
{
cout << "~HeapOnly()" << endl;
}
private:
int _a;
};
int main()
{
//HeapOnly hp1;// error
//static HeapOnly hp2;// error
HeapOnly* ptr = new HeapOnly;
ptr->Delete();
return 0;
}
#include
#include
using namespace std;
class HeapOnly
{
public:
// 提供一个公有的,获取对象的方式,对象控制是new出来的
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
// 防拷贝
HeapOnly(const HeapOnly& hp) = delete;
HeapOnly& operator=(const HeapOnly& hp) = delete;
private:
// 构造函数私有
HeapOnly()
:_a(0)
{}
private:
int _a;
};
int main()
{
/*HeapOnly hp1;
static HeapOnly hp2;
HeapOnly* hp3 = new HeapOnly;
delete hp3;*/
HeapOnly* hp3 = HeapOnly::CreateObj();
//HeapOnly copy(*hp3);
delete hp3;
return 0;
}
注意: 如果需要派生该类,就不能将构造函数设为私有或删除。如果需要限制派生类对象只能在堆上构造,可以在派生类中重载 new 和 delete 运算符,强制所有派生类对象都通过堆来创建和销毁
#include
#include
using namespace std;
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly st;
return st;
}
// 不能防拷贝
//StackOnly(const StackOnly& st) = delete;
//StackOnly& operator=(const StackOnly& st) = delete;
void* operator new(size_t n) = delete;
private:
// 构造函数私有
StackOnly()
:_a(0)
{}
private:
int _a;
};
int main()
{
/*StackOnly st1;
static StackOnly st2;
StackOnly* st3 = new StackOnly;*/
StackOnly st1 = StackOnly::CreateObj();
// 拷贝构造
static StackOnly copy2(st1); // 不好处理,算是一个小缺陷
//StackOnly* copy3 = new StackOnly(st1);
return 0;
}
第一: private,public,protected的访问范围:
类中的函数 友元函数 子类函数 类的对象
private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
第二:类的继承后方法属性变化:
class test{
public:
test(const test& a)
{
cout<<"拷贝构造函数"<
有时候时候需要加上const修饰符的原因是为了确保被拷贝的对象在拷贝过程中不会被修改。
可以提高代码的可靠性和安全性(特别是在多线程的情况下)
就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会
产生出不同的状态
静态多态(编译阶段确定)
动态多态(运行阶段确定)
派生类 和 虚函数 的使用
栈区:由编译器自动分配和释放,存放为运行函数分配的局部变量,函数参数,返回数据,返回地址等,其操作类似于数据结构中的栈。
堆区:一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
全局区(静态区static):存放全局变量,静态变量,常量。结束后由系统释放。
常量区(字符串常量区):存放常量字符串,程序结束后有系统释放。
代码区:存放函数体(类成员函数和全局区)的二进制代码。+
声明是告诉编译器名字的存在,
而定义是为名字分配内存并实现其功能。
在使用变量或函数之前,必须先进行声明或定义。
strcpy和memcpy都是在C语言和C++语言中用于复制内存块的函数,但它们在使用和效率上有所不同。
但他们两者在拷贝时,如果源地址不够时,都会出现内存越界和缓冲区溢出问题
编译过程分为四个过程:编译(编译预处理、编译、优化),汇编,链接
栈在内存中是连续的一块空间(向低地址扩展)最大容量是系统预定好的,堆在内存中的空间(向高地址扩展)是不连续的。
如果在头文件中定义全局变量,当该头文件被多个文件 include
时,该头文件中的全局变量就会被定义多次,导致重复定义,因此不能再头文件中定义局变量。
什么是内存对齐?内存对齐的原则?为什么要进行内存对齐,有什么优点?
内存对齐:编译器将程序中的每个“数据单元”安排在字的整数倍的地址指向的内存之中
内存对齐的原则:
进行内存对齐的原因:(主要是硬件设备方面的问题)
说明:类的大小是指类的实例化对象的大小,用 sizeof
对类型名操作时,结果是该类型的对象的大小。
计算原则:
unique_ptr: 禁掉了拷贝构造和赋值重载
shared_ptr: 共同管理一段空间,引用计数 记录管理者
weak_ptr: 为了解决shared_ptr循环引用的问题,使它的next和prev不增加计数
unique_ptr: 禁掉了拷贝构造和赋值重载
shared_ptr: 共同管理一段空间,引用计数 记录管理者
weak_ptr: 为了解决shared_ptr循环引用的问题,是它的next和prev不增加计数
auto
关键字:自动类型推导,编译器会在 编译期间 通过初始值推导出变量的类型,通过 auto
定义的变量必须有初始值。[捕捉列表](参数列表)mutable->返回值类型{函数体实现}
不能取地址的叫常量,只能用右值引用
// 对右值的左值引用
// double& r1 = 1.1 + 2.2;// error
const double& r1 = 1.1 + 2.2;
// 对左值的右值引用
//int&& rr5 = b;// error
int&& rr5 = move(b);
出现的原因 :
具体使用是:
语言自身:
C 语言是面向过程的编程,它的主要特点是函数
C++ 是面向对象的编程, 它的主要特点是类
应用领域:
C 语言主要用于嵌入式领域,驱动开发等与硬件直接打交道的领域,
C++ 可以用于应用层开发,用户界面开发等与操作系统打交道的领域
C++ 对 C 的“增强”,表现在以下几个方面:
类型检查更为严格。增加了面向对象的机制、泛型编程的机制(Template)、异常处理、运算符重载、标准模板库(STL)、命名空间(避免全局命名冲突)
面向对象:对象是指具体的某一个事物,这些事物的抽象就是类,类中包含成员变量和成员方法
封装:将具体的实现过程和数据封装成一个函数,只能通过接口进行访问,降低耦合性。
继承:子类使用父类的方法
多态:去完成某个行为,当不同的对象去完成时会产生出不同的状态
strlen
是头文件 中的函数,sizeof
是 C++ 中的运算符。strlen
测量的是字符串的实际长度(其源代码如下),以 \0
结束。sizeof
测量的是字符数组的分配大小。#include
#include
using namespace std;
class A
{
public:
int var;
explicit A(int tmp)
{
var = tmp;
cout << var << endl;
}
};
int main()
{
A ex(100);
A ex1 = 10; // error: conversion from 'int' to non-scalar type 'A' requested
return 0;
}
作用: 避免编译器进行隐式类型转换
作用: 定义静态变量,静态函数
静态成员变量
静态成员变量是在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化的时候不要出现 static 关键字和private、public、protected 访问规则。
静态成员变量相当于类域中的全局变量,被类的所有对象所共享,包括派生类的对象。
静态成员函数
相同点:
static
全局变量都是静态存储方式。不同点:
const 成员变量:
const 成员函数:
区别
const
的优点:
#include
#define MAX(X, Y) ((X)>(Y)?(X):(Y))
#define MIN(X, Y) ((X)<(Y)?(X):(Y))
using namespace std;
int main ()
{
int var1 = 10, var2 = 100;
cout << MAX(var1, var2) << endl;
cout << MIN(var1, var2) << endl;
return 0;
}
/*
程序运行结果:
100
10
*/
作用:
使用方法:
new 是 C++ 中的关键字,用来动态分配内存空间,实现方式如下:
int *p = new int[5];
delete 的实现原理:
delete 和 delete [] 的区别:
相同点:都是从堆上申请空间,并且需要用户手动释放。
不同点:
malloc和free是函数,new和delete是操作符
malloc申请的空间不会初始化,new申请的空间会初始化,即调用构造函数
malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
malloc的返回值是void*,在使用时必须强转,new不需要,因为new后跟的是空间的类型
malloc申请失败时,返回的是NULL,因此使用时必须判空,而new不需要,但是new需要捕获异常
malloc 的原理:
struct
。// 大小 = 所有变量类型最大的那个 的 整数倍
typedef union
{
char c[10];
int i;
double d; // double 8 字节,按该类型的倍数分配大小
} u22;
// 需要遵循内存对齐规则
typedef struct s2
{
char c; // 1 字节
char cc; // 1(char)+ 1(char)= 2 字节
double d; // 2 + 6(内存对齐)+ 8(double)= 16 字节
} s22;
联合体和结构体都是由若干个数据类型不同的数据成员组成。
使用时,联合体只有一个有效的成员;
而结构体所有的成员都有效。
对联合体的不同成员赋值,将会对覆盖其他成员的值,
而对于结构体的对不同成员赋值时,相互不影响。
联合体的大小为其内部所有变量的最大值,按照最大类型的倍数进行分配大小;
结构体分配内存的大小遵循内存对齐原则。
struct的成员默认是公有的,而类的成员默认是私有的
使用 volatile 关键字的场景:
volatile 关键字和 const 关键字可以同时使用,某种类型可以既是 volatile 又是 const ,同时具有二者的属性。
在C++多线程中,volatile不具有原子性;无法对代码重新排序实施限制。
能干什么:告诉编译器不要在此内存上做任何优化。如果对内存有只写未读的等非常规操作,如
x=10;
x=20;
编译器会优化为:
x=20;
volatile 就是阻止编译器进行此类优化。
当 C++ 程序 需要调用 C 语言编写的函数,
// 可能出现在 C++ 头文件中的链接指示
extern "C"{
int strcmp(const char*, const char*);
}
虚函数:被 virtual
关键字修饰的成员函数,就是虚函数。
virtual
关键字,纯虚函数定义时除了加上virtual
关键字还需要加上 =0
;虚函数表建立的时间:编译阶段,即程序的编译过程中会将虚函数的地址放在虚函数表中。
虚表指针保存的位置:虚表指针存放在对象的内存空间中最前面的位置,这是为了保证正确取到虚函数的偏移量。
= delete
修饰符,可以达到虽然声明了构造函数但禁止使用的目的。#include
using namespace std;
class Base final
{
};
class Derive: public Base{ // error: cannot derive from 'final' base 'Base' in derived type 'Derive'
};
int main()
{
Derive ex;
return 0;
}
被final关键字修饰的基类 不能被派生类继承
#include
#include
using namespace std;
int main()
{
// 左值: 能取地址
int* p = new int(0);
int b = 1;
const int c = 2;
// 对左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
// 右值:不能取地址
10;
1.1 + 2.2;
// 对右值的右值引用
int&& rr1 = 10;
double&& rr2 = 1.1 + 2.2;
// 对右值的左值引用
// double& r1 = 1.1 + 2.2;// error
const double& r1 = 1.1 + 2.2;
// 对左值的右值引用
//int&& rr5 = b;// error
int&& rr5 = move(b);
return 0;
}
void *p = malloc(size);
free(p);
// 此时,p 指向的内存空间已释放, p 就是悬空指针。
void *p;
// 此时 p 是“野指针”。
而NULL 本质上是 0,在函数调用过程中,若出现函数重载并且传递的实参是 NULL,可能会出现不知道调那个函数的问题
static_cast关键字 -> 隐式类型转换
reinterpret_cast关键字 -> 强制类型转换
const_cast关键字->取消变量的const属性
dynamic_cast关键字->父类指针 转换 子类指针(保证安全性)
实例化: 函数模板实例化后是一个函数,类模板实例化后是一个类。
可变参数模板:接受可变数目参数的模板函数或模板类。将可变数目的参数被称为参数包,包括模板参数包和函数参数包。
特化分为全特化和偏特化:
函数模板只能全特化;而类模板可以全特化,也可以偏特化
容器:涉及到 STL 中的容器,例如:vector、list、map 等,可选其中熟悉底层原理的容器进行展开讲解。
迭代器:在无需知道容器底层原理的情况下,遍历容器中的元素。
模板:可参考本章节中的模板相关问题。
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int i = 0;
int n = 100;
mutex mtx;
condition_variable cv;// 条件变量
bool ready = true;
// t1打印奇数
thread t1([&]() {
while (i < n)
{
{
unique_lock lock(mtx);
cv.wait(lock, [&ready]() {return !ready; });// 等待线程
cout << "t1--" << this_thread::get_id() << ":" << i << endl;
i += 1;
ready = true;
cv.notify_one();// 解除线程等待
}
//this_thread::yield();
this_thread::sleep_for(chrono::microseconds(100));
}
});
// t2打印偶数
thread t2([&]() {
while (i < n)
{
{
unique_lock lock(mtx);
cv.wait(lock, [&ready]() {return ready; });
cout << "t2--" << this_thread::get_id() << ":" << i << endl;
i += 1;
ready = false;
cv.notify_one();
}
}
});
this_thread::sleep_for(chrono::seconds(3));
cout << "t1:" << t1.get_id() << endl;
cout << "t2:" << t2.get_id() << endl;
t1.join();
t2.join();
return 0;
}
当这个lambda表达式返回的是false时
阻塞当前线程,并自动调用lock.unlock(),允许其他锁定的线程继续执行
cv.notify_one();
唤醒当前线程并自动调用lock.lock();就只允许自己一个线程执行
那两种模式都是将构造函数私有化,自己实现一个构造生成一个静态对象
class Singleton
{
public:
static Singleton* GetInstance()
{
return &m_instance;
}
private:
// 构造函数私有
Singleton() {};
// C++11 : 防拷贝
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
static Singleton m_instance;// 声明
};
Singleton Singleton::m_instance;// 定义
优点:简单
缺点:可能会导致进程启动慢,且如果 有多个单例类对象实例启动顺序 不确定。
总结: 如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好
#include
#include
#include
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance()
{
// 保护第一次,后续不需要加锁
// 双检查加锁
if (_pInstance == nullptr)
{
unique_lock lock(_mtx);
if (_pInstance == nullptr)
{
_pInstance = new Singleton;
}
}
return _pInstance;
}
private:
// 构造函数私有
Singleton(){};
// C++11
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
static Singleton* _pInstance;
static mutex _mtx;
};
Singleton* Singleton::_pInstance = nullptr;
mutex Singleton::_mtx;
int main()
{
Singleton::GetInstance();
Singleton::GetInstance();
return 0;
}
#include
#include
using namespace std;
class MemoryPool
{
public:
static MemoryPool* GetInstance()
{
if (_pinst == nullptr) {
_pinst = new MemoryPool;
}
return _pinst;
}
void* Alloc(size_t n)
{
void* ptr = nullptr;
// ....
return ptr;
}
void Dealloc(void* ptr)
{
// ...
}
// 实现一个内嵌垃圾回收类
class CGarbo {
public:
~CGarbo()
{
if (_pinst)
delete _pinst;
}
};
private:
// 构造函数私有化
MemoryPool()
{
// ....
}
char* _ptr = nullptr;
// ...
static MemoryPool* _pinst; // 声明
};
// 定义
MemoryPool* MemoryPool::_pinst = nullptr;
// 回收对象,main函数结束后,他会调用析构函数,就会释放单例对象
static MemoryPool::CGarbo gc;
int main()
{
void* ptr1 = MemoryPool::GetInstance()->Alloc(10);
MemoryPool::GetInstance()->Dealloc(ptr1);
}
原文出处: 整理的C++面经(较全)-CSDN博客