C++ 基础问题杂

记录一下找到的网上的关于c++一些知识总结
关键字Static的作用以及加static和不加static的区别?
1、 函数加上static,该函数就失去了全局可见性,只在该函数所在的文件作用域内可见;
2、在类的成员函数前面加上static标识符,成员函数是属于类的而非对象的。

static 关键字:
当static关键字修饰局部变量时,只会对该变量初始化一次。
当static关键字修饰局部变量时,该变量在程序中只有一份内存。
当static关键字修饰局部变量时,该变量的作用域不会改变。
当static关键字修饰局部变量时,该变量的生命周期被延长,直到程序结束才销毁。
当用static关键字修饰全局变量时,该变量的作用域仅限于当前文件,工程中的其他文件不可以访问到该全局变量。
1、隐藏
2、保持变量内容的持久
3、默认初始化为0
4、C++中类成员函数声明static

单例模式,饿汉与懒汉模式
https://www.cnblogs.com/sunchaothu/p/10389842.html

饿汉模式:

#include <iostream>
// version1:
// with problems below:
// 1. thread is not safe
// 2. memory leak

class Singleton{
private:
    Singleton(){
        std::cout<<"constructor called!"<<std::endl;
    }
    Singleton(Singleton&)=delete;
    Singleton& operator=(const Singleton&)=delete;
    static Singleton* m_instance_ptr;
public:
    ~Singleton(){
        std::cout<<"destructor called!"<<std::endl;
    }
    static Singleton* get_instance(){
        if(m_instance_ptr==nullptr){
              m_instance_ptr = new Singleton;
        }
        return m_instance_ptr;
    }
    void use() const { std::cout << "in use" << std::endl; }
};

Singleton* Singleton::m_instance_ptr = nullptr;

int main(){
    Singleton* instance = Singleton::get_instance();
    Singleton* instance_2 = Singleton::get_instance();
    return 0;
}

优化下,线程安全

#include <iostream>
#include <memory> // shared_ptr
#include <mutex>  // mutex

// version 2:
// with problems below fixed:
// 1. thread is safe now
// 2. memory doesn't leak

class Singleton{
public:
    typedef std::shared_ptr<Singleton> Ptr;
    ~Singleton(){
        std::cout<<"destructor called!"<<std::endl;
    }
    Singleton(Singleton&)=delete;
    Singleton& operator=(const Singleton&)=delete;
    static Ptr get_instance(){

        // "double checked lock"
        if(m_instance_ptr==nullptr){
            std::lock_guard<std::mutex> lk(m_mutex);
            if(m_instance_ptr == nullptr){
              m_instance_ptr = std::shared_ptr<Singleton>(new Singleton);
            }
        }
        return m_instance_ptr;
    }


private:
    Singleton(){
        std::cout<<"constructor called!"<<std::endl;
    }
    static Ptr m_instance_ptr;
    static std::mutex m_mutex;
};

// initialization static variables out of class
Singleton::Ptr Singleton::m_instance_ptr = nullptr;
std::mutex Singleton::m_mutex;

int main(){
    Singleton::Ptr instance = Singleton::get_instance();
    Singleton::Ptr instance2 = Singleton::get_instance();
    return 0;
}

shared_ptr和mutex都是C++11的标准,以上这种方法的优点是

基于 shared_ptr, 用了C++比较倡导的 RAII思想,用对象管理资源,当 shared_ptr 析构的时候,new 出来的对象也会被 delete掉。以此避免内存泄漏。
加了锁,使用互斥量来达到线程安全。这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,锁的开销毕竟还是有点大的。
不足之处在于: 使用智能指针会要求用户也得使用智能指针,非必要不应该提出这种约束; 使用锁也有开销; 同时代码量也增多了,实现上我们希望越简单越好。

还有更加严重的问题,在某些平台(与编译器和指令集架构有关),双检锁会失效!具体可以看这篇文章,解释了为什么会发生这样的事情。

原博客推荐的饿汉:

#include <iostream>

class Singleton
{
public:
    ~Singleton(){
        std::cout<<"destructor called!"<<std::endl;
    }
    Singleton(const Singleton&)=delete;
    Singleton& operator=(const Singleton&)=delete;
    static Singleton& get_instance(){
        static Singleton instance;
        return instance;

    }
private:
    Singleton(){
        std::cout<<"constructor called!"<<std::endl;
    }
};

int main(int argc, char *argv[])
{
    Singleton& instance_1 = Singleton::get_instance();
    Singleton& instance_2 = Singleton::get_instance();
    return 0;
}

用了static 特性保证
通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性);
不需要使用共享指针,代码简洁;
注意在使用的时候需要声明单例的引用 Single& 才能获取对象。

饿汉模式

饿汉模式

就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。

实现:

    构造函数私有   

    定义一个私有静态成员变量,类型为当前类类型 

    在类外通过构造函数初始化  

    防拷贝:拷贝构造定义成私有,只声明不实现,或者定义为delete函数

    定义一个static共有的成员函数,函数内部返回单例,返回值类型为引用/指针

饿汉模式

优点:简单

缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。

class Singleton
 {
 public:
 static Singleton* GetInstance()
 {
 return &m_instance;
 }
 
 private:
 // 构造函数私有
 Singleton(){};
 
 // C++98 防拷贝
 Singleton(Singleton const&); 
 Singleton& operator=(Singleton const&); 
 
 // or
 
 // C++11
 Singleton(Singleton const&) = delete; 
 Singleton& operator=(Singleton const&) = delete; 
 
 static Singleton m_instance;
 };
 
 Singleton Singleton::m_instance; // 在程序入口之前就完成单例对象的初始化

懒汉模式

   如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等 等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时 非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

实现:

    构造函数私有   

    定义一个私有静态成员变量,类型为当前类类型指针

    在类外初始化为空指针(nullptr)

    防拷贝:拷贝构造定义成私有,只声明不实现,或者定义为delete函数

    定义一个static共有的成员函数,函数内部返回单例,返回值类型为指针

懒汉

优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。

缺点:复杂

#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
class Singleton
{
public:
 static Singleton* GetInstance() {
 // 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全
 if (nullptr == m_pInstance) {
 m_mtx.lock();
 if (nullptr == m_pInstance) {
 
 m_pInstance = new Singleton();
 }
 m_mtx.unlock();
 }
 return m_pInstance;
 }
 // 实现一个内嵌垃圾回收类 
 class CGarbo {
 public:
 ~CGarbo(){
 if (Singleton::m_pInstance)
 delete Singleton::m_pInstance;
 }
 };
 // 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
 static CGarbo Garbo;
private:
 // 构造函数私有
 Singleton(){};
 // 防拷贝
 Singleton(Singleton const&);
 Singleton& operator=(Singleton const&);
 static Singleton* m_pInstance; // 单例对象指针
 static mutex m_mtx; //互斥锁
};
Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;
void func(int n)
{
 cout<< Singleton::GetInstance() << endl;
}
// 多线程环境下演示上面GetInstance()加锁和不加锁的区别。
int main()
{
 thread t1(func, 10);
 thread t2(func, 10);
 t1.join();
 t2.join();
 cout << Singleton::GetInstance() << endl;
 cout << Singleton::GetInstance() << endl;
}

https://www.cnblogs.com/WindSun/p/11444429.html
C++ 智能指针

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。

为什么要使用智能指针
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

常用的智能指针:
shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
拷贝和赋值。拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象。
get函数获取原始指针
注意不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。

成员函数:

use_count 返回引用计数的个数

unique 返回是否是独占所有权( use_count 为 1)

swap 交换两个 shared_ptr 对象(即交换所拥有的对象)

reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少

get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如
shared_ptr

int main()
{
	string *s1 = new string("s1");

	shared_ptr<string> ps1(s1);
	shared_ptr<string> ps2;
	ps2 = ps1;

	cout << ps1.use_count()<<endl;	//2
	cout<<ps2.use_count()<<endl;	//2
	cout << ps1.unique()<<endl;	//0

	string *s3 = new string("s3");
	shared_ptr<string> ps3(s3);

	cout << (ps1.get()) << endl;	//033AEB48
	cout << ps3.get() << endl;	//033B2C50
	swap(ps1, ps3);	//交换所拥有的对象
	cout << (ps1.get())<<endl;	//033B2C50
	cout << ps3.get() << endl;	//033AEB48

	cout << ps1.use_count()<<endl;	//1
	cout << ps2.use_count() << endl;	//2
	ps2 = ps1;
	cout << ps1.use_count()<<endl;	//2
	cout << ps2.use_count() << endl;	//2
	ps1.reset();	//放弃ps1的拥有权,引用计数的减少
	cout << ps1.use_count()<<endl;	//0
	cout << ps2.use_count()<<endl;	//1
}

weak_ptr
share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

class B;	//声明
class A
{
public:
	shared_ptr<B> pb_;
	~A()
	{
		cout << "A delete\n";
	}
};

class B
{
public:
	shared_ptr<A> pa_;
	~B()
	{
		cout << "B delete\n";
	}
};

void fun()
{
	shared_ptr<B> pb(new B());
	shared_ptr<A> pa(new A());
	cout << pb.use_count() << endl;	//1
	cout << pa.use_count() << endl;	//1
	pb->pa_ = pa;
	pa->pb_ = pb;
	cout << pb.use_count() << endl;	//2
	cout << pa.use_count() << endl;	//2
}

int main()
{
	fun();
	return 0;
}

weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。

使用shared_ptr 造成循环引用导致内存泄漏,example:

class CB;
class CA
{
public:
    CA() { cout << "CA() called! " << endl; }
    ~CA() { cout << "~CA() called! " << endl; }
    void set_ptr(shared_ptr<CB>& ptr) { m_ptr_b = ptr; }
    void b_use_count() { cout << "b use count : " << m_ptr_b.use_count() << endl; }
    void show() { cout << "this is class CA!" << endl; }
private:
    shared_ptr<CB> m_ptr_b;
};

class CB
{
public:
    CB() { cout << "CB() called! " << endl; }
    ~CB() { cout << "~CB() called! " << endl; }
    void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
    void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
    void show() { cout << "this is class CB!" << endl; }
private:
    shared_ptr<CA> m_ptr_a;
};

void test_refer_to_each_other()
{
    shared_ptr<CA> ptr_a(new CA());
    shared_ptr<CB> ptr_b(new CB());

    cout << "a use count : " << ptr_a.use_count() << endl;
    cout << "b use count : " << ptr_b.use_count() << endl;

    ptr_a->set_ptr(ptr_b);
    ptr_b->set_ptr(ptr_a);

    cout << "a use count : " << ptr_a.use_count() << endl;
    cout << "b use count : " << ptr_b.use_count() << endl;
}

结果:

CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 2
b use count : 2

weak_ptr解决循环引用 demo:

class CB
{
public:
    CB() { cout << "CB() called! " << endl; }
    ~CB() { cout << "~CB() called! " << endl; }
    void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
    void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
    void show() { cout << "this is class CB!" << endl; }
private:
    weak_ptr<CA> m_ptr_a;
};
CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 1
b use count : 2
~CA() called!
~CB() called!

,就是为了辅助shared_ptr的,它本身不能直接定义指向原始指针的对象,只能指向shared_ptr对象,同时也不能将weak_ptr对象直接赋值给shared_ptr类型的变量,最重要的一点是赋值给它不会增加引用计数
weak_ptr中只有函数lock和expired两个函数比较重要,因为它本身不会增加引用计数,所以它指向的对象可能在它用的时候已经被释放了,所以在用之前需要使用expired函数来检测是否过期,然后使用lock函数来获取其对应的shared_ptr对象,然后进行后续操作demo:

void test2()
{
    shared_ptr<CA> ptr_a(new CA());     // 输出:CA() called!
    shared_ptr<CB> ptr_b(new CB());     // 输出:CB() called!

    cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
    cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
    
    weak_ptr<CA> wk_ptr_a = ptr_a;
    weak_ptr<CB> wk_ptr_b = ptr_b;

    if (!wk_ptr_a.expired())
    {
        wk_ptr_a.lock()->show();        // 输出:this is class CA!
    }

    if (!wk_ptr_b.expired())
    {
        wk_ptr_b.lock()->show();        // 输出:this is class CB!
    }

    // 编译错误
    // 编译必须作用于相同的指针类型之间
    // wk_ptr_a.swap(wk_ptr_b);         // 调用交换函数

    wk_ptr_b.reset();                   // 将wk_ptr_b的指向清空
    if (wk_ptr_b.expired())
    {
        cout << "wk_ptr_b is invalid" << endl;  // 输出:wk_ptr_b is invalid 说明改指针已经无效
    }

    wk_ptr_b = ptr_b;
    if (!wk_ptr_b.expired())
    {
        wk_ptr_b.lock()->show();        // 输出:this is class CB! 调用赋值操作后,wk_ptr_b恢复有效
    }

    // 编译错误
    // 编译必须作用于相同的指针类型之间
    // wk_ptr_b = wk_ptr_a;


    // 最后输出的引用计数还是1,说明之前使用weak_ptr类型赋值,不会影响引用计数
    cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
    cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
}

1.weak_ptr虽然是一个模板类,但是不能用来直接定义指向原始指针的对象。
2.weak_ptr接受shared_ptr类型的变量赋值,但是反过来是行不通的,需要使用lock函数。
3.weak_ptr设计之初就是为了服务于shared_ptr的,所以不增加引用计数就是它的核心功能。
4.由于不知道什么之后weak_ptr所指向的对象就会被析构掉,所以使用之前请先使用expired函数检测一下。
(https://blog.csdn.net/albertsh/article/details/82286999)
volatile有什么含义?有什么用法?
一个变量也许会被后台程序改变。
关键字volatile与const绝对独立。它指示一个变量也许会被某种方式修改,这种方式按照正常程序流程分析是无法预知的(例如,一个变量也许会被一个中断服务程序所修改)
注:变量如果加了voletile修饰,则会从内存中重新装载内容,而不是直接从寄存器中拷贝内容。
下面是volatile变量的几个例子:
1.并行设备的硬件寄存器(如:状态寄存器)
2.一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3.多线程应用中被几个任务共享的变量

struct 与 class区别
1、默认的继承权限
struct默认是公有继承(public),class默认是私有继承(private)

2、关于默认访问权限
class中默认的成员访问权限是private的,而struct中则是public的。

3、关于大括号初始化问题
在C++中class和struct的区别:
在C++中对struct的功能进行了扩展,struct可以被继承,可以包含成员函数,也可以实现多态,当用大括号对其进行初始化需要注意:

当struct和class中都定义了构造函数,就不能使用大括号对其进行初始化
若没有定义构造函数,struct可以使用{ }进行初始化,而只有当class的所有数据成员及函数为public时,可以使用{ }进行初始化
所以struct更适合看成是一个数据结构的实现体,class更适合看成是一个对象的实现体。
4、关于模板
在模板中,类型参数前面可以使用class或typename,如果使用struct,则含义不同,struct后面跟的是“non-type template parameter”,而class或typename后面跟的是类型参数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值