C++基础

C++基础

一、C++ 三大特性

1、继承

  • 被继承的是父类(基类),继承的是子类(派生类),子类拥有父类的所有特性;继承方式包括公有继承、私有继承和保护继承,默认的继承方式为私有继承;
  • 公有继承(public)中父类的公有成员和保护成员在子类中不变,私有成员在子类中不可访问(只能通过基类提供的方法访问)。
  • 私有继承(private)中父类的公有和保护成员在子类中变为私有成员,但私有成员在子类中不可访问。
  • 保护继承(protected)中父类的公有成员和保护成员在子类中变为保护成员,但私有的在子类中不可访问。
  • c++中允许单继承和多继承:https://blog.csdn.net/zb1593496558/article/details/80006640
    • 单继承:一个子类只继承一个父类
    • 一个子类可以继承多个父类:菱形继承会出现歧义,即子类中有来自两个父类的基类数据成员,在编译时就会不通过;可以通过定义虚基类来解决该问题,虚继承机制中维护了一张虚基类表。
    • 派生类不会继承父类的构造函数和析构函数;派生类的构造函数应该显示地调用基类的构造函数,析构函数不需要,调用反而会造成内存泄漏。
  • 优点:继承减少了重复代码、继承是多态的前提;
  • 缺点:继承在编译时刻定义了,无法在运行时刻改变父类继承的实现;增加了类之间的耦合,父类的修改可能影响子类的行为,如果不符合需求可能要重写类体系,这种依赖关系限制了灵活性和可扩展性。

2、多态

  • 多态性:对不同类的对象发出相同的消息会得到不同的结果;
  • C++多态分为两种,静态多态和动态多态:
    • 静态多态:主要通过模板来实现(函数模板,类模板);
    • 动态多态:主要通过虚函数来实现。基类中存在虚函数(一般为纯虚函数),子类重载这些虚函数,在程序开发时,使用基类的指针或者以用指向子类的对象,可以调用子类对应重载过的函数。该过程是在程序执行期动态绑定的。
  • 优点:极大提高了代码的可重用性;提高了代码的可维护性,与可扩展性;
  • 缺点:开发时比较困难;模板定义在头文件中,工程项目较大后编译时间开销较大。

3、封装

  • 封装性:隐藏类的属性和实现细节,仅仅对外提供接口;
  • 由编译器去识别关键字public、private和protected来实现。私有成员是封装体内被隐藏的部分,只有类体内的成员函数才可以访问私有成员,类体外的函数不能访问,公有成员是封装体与外界的一个接口,保护成员只有该类的成员函数和该类的派生类才可以访问
  • 优点:隔离变化,提高可维护性和重用性;提高类安全性;
  • 缺点:封装太多影响效率。

4、重载、覆盖和隐藏

https://blog.csdn.net/weixin_43217963/article/details/88647287

  • 重载:
    • 作用域相同(在同一个类中)
    • 函数名称相同
    • 参数列表不相同
    • virtual关键字可有可无
  • 覆盖:
    • 作用域不同(在基类和派生类中)
    • 函数名相同
    • 参数列表相同
    • 基类中函数必须有virtual关键字
  • 隐藏:
    • 作用域不同(在基类和派生类中)
    • 如果派生类的函数与基类的函数名相同,但是参数列表不同,这时不论是否由virtual关键字,基类函数都将被隐藏,此时就访问不了基类的函数了;
    • 如果派生类的函数与基类的函数名相同,并且参数列表也相同,但是基类函数没有virtual关键字,基类函数会被隐藏。

二、C++类所占内存大小的计算

三、虚函数实现原理以及多态中的指针转换

https://www.bilibili.com/video/BV15g4y1a7F3?from=search&seid=11917085418676393440
类→结构体:成员变量,虚函数表 const funcTable* vt;
struct FuncTable {
void(* func)(void* _ptr);
}

const static FuncTable _childFuncTable = {
_childFunc(void* _ptr)
}

1、不是所有函数都可以做虚函数?

  • 构造函数:不能被继承;构造函数时,函数还没实例化,即虚函数表还没有建立,无法通过虚函数表指针寻找虚函数;
  • 内联成员函数:内联要求在编译时展开该函数,而虚函数要求动态绑定;
  • 静态成员函数:静态函数被类成员共享、只有一份且不可继承;
  • 友元函数:不属于成员函数、无法继承;
  • 普通函数:不能被继承、不能被重写;编译时已经被绑定了;
  • 析构函数常常为虚函数:因为通常多态是通过基类指针指向子类对象实现的,如果析构函数不是虚函数,在释放内存时不能真确识别对象的类型,因此就不能正确的销毁;

https://blog.csdn.net/gaojing303504/article/details/81411036

C++新特性

一、C++11 智能指针

C++11 智能指针介绍

智能指针主要用于管理在堆上分配的内存,其将普通指针封装成一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。

为什么使用智能指针

智能指针的作用是管理一个指针;
主要解决申请空间在函数执行结束时忘记释放导致内存泄漏的问题。
因为智能指针是一个类,在超出了类实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放内存空间,不需要手动释放。

auto_ptr

C++11中已经抛弃了auto_ptr,采用所有权模式。
auto_ptr p1(new string("Hello."));
auto_ptr p2;
p2 = p1; //auto_ptr编译时不会报错,但是运行时会出错
因为p2剥夺了p1的所有权,当程序运行访问p1时会出现错误,所以auto_ptr会存在内存崩溃的问题。

unique_ptr

替换auto_ptr的实现方式,实现了独占适拥有或者严格拥有的概念,保证同一时间内只有一个智能指针指向特定对象,在一定程度上避免资源泄露。
auto_ptr p1(new string("Hello."));
auto_ptr p2;
p2 = p1; // #1 不允许编译时会出错
unique_ptr pu3;
pu3 = unique_ptr(new string("You.")); // #2 允许
也是采用所有权模式,在#1中编译器认为p2 = p1不合法,所以unique_ptr相对于auto_ptr更加安全。但是unique_ptr允许临时右值的赋值如#2中所示,但如果unique_ptr已经存在一段时间,编译器将禁止这样做例如#1中所示。
如果实在是想要重新赋值unique_ptr,可以采用move函数,将p1的所有权转让给p2,但是此时遗留下来的悬挂的p1很危险,需要将其指向另一个对象。这样的操作提高了unique_ptr操作的灵活性。
unique_ptr ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia);
cout << *ps2 << *ps1 << endl;
(另外boost库的boost::scoped_ptr也是一个独占性的智能指针,但是其不允许转移所有权)

shared_ptr

shared_ptr是实现了共享式拥有的概念,多个智能指针可以指向相同的对象,该对象和其相关资源会在“最后一个引用结束时”被释放。其采用计数的方式来表明该资源被几个指针共享,可以通过成员函数use_count()来查看资源的所有者个数。
shared_ptr可以通过new来构造,也可以通过传入auto_ptr,unique_ptr,weark_ptr来构造。
当调用release()时,当前指针会释放资源所有权,计数count减一,当计数减到零时,资源会被释放。
shared_ptr是为了解决auto_ptr在对象所有权上地局限性,在借助引用计数方法的前提下,提供了可以共享所有权地智能指针。
成员函数:
  • use_count:返回引用计数的个数
  • unique:返回是否独占所有权(use_count == 1 ?)
  • swap:交换两个shared_ptr对象(即交换所拥有的对象)
  • reset:放弃内部对象的所有权或拥有对象的变更,会引起原有对象的引用计数减少
  • get:返回内部对象(指针),由于已经重载了()操作符所以get()与单独使用()是等价的,例如:
shared_ptr sp(new int(1));
例子:
int main()
{
    string* s1 = new string("s1");

    shared_ptr ps1(s1);
    shared_ptr 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 ps3(s3);

    cout << ps1.get() << endl;	//012B0BA0
    cout << ps3.get() << endl;	//012B0D50
    swap(ps1, ps3);	//交换所拥有的对象
    cout << ps1.get() << endl;	//012B0D50
    cout << ps3.get() << endl;	//012B0BA0

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

weak_ptr

shared_ptr还是会有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方时,会造成循环引用,使得计数失效。
weak_ptr是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象,负责该对象内存管理的是那个强引用的shared_ptr,weak_ptr只提供了管理对现象的一个访问手段。weak_ptr设计的目的是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它只可以从一个shared_ptr或者另一个weak_ptr对象构造,其构造和析构不会引起引用计数的增加或减少。
weak_ptr适用于解决shared_ptr相互引用时的死锁问题,如果两个shared_ptr相互引用,那么这两个智能指针的引用计数永远不可能下降到0,资源永远不会释放。
weak_ptr是针对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它也可以通过lock()函数来获得shared_ptr。
例子:
class B;	//声明
class A
{
public:
    shared_ptr pb_;
    ~A()
    {
        cout << "A delete\n";
    }
};

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

void fun()
{
    shared_ptr pb(new B());
    shared_ptr 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;
}
因为pa所指对象中的shared_ptr指向pb所指的对象,所以pb的引用计数为2,同理pa的引用计数也为2,示意图如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VhVnHLN1-1644655924598)(shared_ptr.png “shared_ptr circel reference”)]

当要跳出函数时,智能指针pa,pb析构两个资源引用计数会减1,但是两者引用计数为1,导致跳出函数时资源没有被释放(A、B的析构函数没有被调用),造成能存泄露,只需要把其中一个成员从shared_ptr改为weak_ptr即可。这样资源B的引用计数以开始就为1,当pb析构时,B的计数变为0,B得到释放,B的释放使得A的计数减为1,同时pa析构时使得A的引用计数减为0,对象A的资源得到释放。
注意:我们不能通过weak_ptr直接访问对象,如果要访问需先将其转换为shared_ptr,即调用lock函数。例如:
shared_ptr p = pa->pb.lock();
p->print();
weak_ptr的成员函数:
  • expired:用于检查所指向的对象是否已经被释放
  • lock:用于获取所指向对象的强引用(shared_ptr),如果expired为true,则返回一个空的shared_ptr
  • use_count
  • reset:将weak_ptr置空
    weak_ptr支持拷贝和赋值,但是不影响对应的shared_ptr内部的对象的计数

二、C++中的内置函数

位运算相关

  • __builtin_popcount(int)

    • 该函数是GCC中的一个内建函数,它可以精确计算数中1的个数,采用了基于表的方法来进行位搜索,而不是清除最低的1位计数。表中存有所有的8位整数为key,1的个数为value的表,如果64位整数则需要查8次表。

一些简易宏定义

字符串判断宏定义
  • isalpha():判断是否为字母
  • isalnum():判断是否为字母或者数字
  • islower():判断是否为小写字母
  • isupper():判断是否为大写字母
  • isdigit():判断是否为数字
  • isspace():判断是否为空格

三、lambda表达式

  • 用于定义并创建匿名的函数对象,以简化编程工作。

  • 语法: [函数对象参数] (操作符重载函数参数) mutable或exception声明 -> 返回值类型 {函数体}

  • 语法分析

    • 函数对象参数
      • 函数对象参数是用于传递给编译器自动生成的函数对象类的构造函数用的,必须存在不能省略。
      • 函数对象参数只能使用那些定义到Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。可以有以下形式:
        • 空:没有任何函数对象参数
        • = :值传递方式的所有可见局部变量;
        • & :引用传递方式的所有可见局部变量;
        • this :lambda所在类的成员变量;
        • a :将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的,要修改传递进来的拷贝,要添加mutable修饰符。
        • &a :将a按引用传递;
        • a, &b :a按值传递,b按引用传递;
        • =, &a, &b :除a和b按引用传递外,其他参数都按值进行传递
        • &, a, b :除a和b按值进行传递外,其他参数都按引用传递
    • 操作符重载函数
      • 标识重载的()操作符的参数,没有参数时可以省略。参数可以通过按值(a,b)或者引用(&a,&b)方式进行传递。
    • mutable或exception声明
      • 该部分可以省略
      • 如果有mutable修饰符,可以修改传进来的参数值的拷贝
      • exception声明:用于指定函数抛出的异常
    • 返回值类型
      • 这部分可以省略
      • 当返回值为void或者函数体中只有一处return的地方,编译器可以自动推断返回值类型。
  • 示例1:

 std::vector some_list;
int total = 0;
for (int i = 0; i < 5; ++i) some_list.emplace_back(i);
std::for_each(begin(some_list), end(some_list), [&total](int x)
{
    total += x;
});
  • 示例2:
 std::vector some_list;
int total = 0;
int value = 5;
std::for_each(begin(some_list), end(some_list), [&, value, this](int x){
    total += x * value * this->some_func();
});
  • lambda函数是一个依赖于实现的函数对象类型,这个类型的名字只有编译器知道,如果用户想把lambda函数作为一个参数来传递,那么形参的类型必须是模板类型或者必须能创建一个std::function类似的对象去捕获lambda函数。使用auto关键字可以用于帮助存储lambda函数。

auto my_lambda_func = [&](int x) { /*...*/ };
auto my_onheap_lambda_func = new auto([=](int x) { /*...*/ });

四、decltype关键字

https://www.cnblogs.com/cauchy007/p/4966485.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值