C++进阶知识点

不放之前的东西了,问就是不会

作为22届毕业生,正式入职了4个月后被裁员,不过幸运的是,一个月内找到了现在这家愿意接纳我的公司,平台也够大,唯一的不足就是在北京,上份工作主要使用的是python语言和相关框架来写web应用,这份工作目前的task是写c++来对数据进行处理,我深知自己的C++知识的不足,之前的比赛用的其实就是C和C++中的非线程安全的stl,所以正好趁着这个机会好好学下C++ and 多线程

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

const 用法

如果const位于星号*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;

如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。

const int func()   返回一个常量

int func(const int a) a 在函数体内是个常量无法修改

int func() const 这种用法一般是类内函数,表示类成员变量无法在函数体内修改

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

explicit关键字

加了explicit关键字后,可防止以上隐式类型转换发生

class Circle 
    { 
    public: 
       explicit Circle(double r) : R(r) {} 
        explicit Circle(int x, int y = 0) : X(x), Y(y) {} 
       explicit Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {} 
    private: raw_msg
        double R; 
        int    X; 
       int    Y; 
   }; 
    
   int _tmain(int argc, _TCHAR* argv[]) 
   { 
   //一下3句,都会报错 
       //Circle A = 1.23;  
       //Circle B = 123; 
       //Circle C = A; 
        
   //只能用显示的方式调用了 
   //未给拷贝构造函数加explicit之前可以这样 
            Circle A = Circle(1.23); 
           Circle B = Circle(123); 
           Circle C = A; 
    
   //给拷贝构造函数加了explicit后只能这样了 
            Circle A(1.23); 
           Circle B(123); 
           Circle C(A); 
       return 0; 
   } 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

override关键字

表示该函数是重写父类的虚函数

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`

DISALLOW_COPY_AND_ASSIGN 类内使用,用于关闭类的拷贝构造和赋值构造

深拷贝和浅拷贝

比如类成员构造函数需要开辟内存空间

浅拷贝复制的是指针地址

深拷贝是新开辟一段内存空间,将值全部复制过去

函数重载

函数重载一定要保证形参表不同,但如果形参表的变量前加一个const,这两个函数还可以重载么?

答案是如果是指针或引用就可以,如果是基本类型变量不可以

指针常量和常量指针

Int *const p 常量指针(不能修改方向)

Int const *p 指针常量(不能改变指向的值)

堆与栈内存地址的区别

1.申请方式:栈是系统自动分配,堆是程序员主动申请。

2.申请后系统响应:分配栈空间,如果剩余空间大于申请空间则分配成功,否则分配失败栈溢出;申请堆空间,堆在内存中呈现的方式类似于链表(记录空闲地址空间的链表),在链表上寻找第一个大于申请空间的节点分配给程序,将该节点从链表中删除,大多数系统中该块空间的首地址存放的是本次分配空间的大小,便于释放,将该块空间上的剩余空间再次连接在空闲链表上。

3.栈在内存中是连续的一块空间(向低地址扩展)最大容量是系统预定好的,堆在内存中的空间(向高地址扩展)是不连续的。

申请效率:栈是有系统自动分配,申请效率高,但程序员无法控制;堆是由程序员主动申请,效率低,使用起来方便但是容易产生碎片。

4.存放的内容:栈中存放的是局部变量,函数的参数;堆中存放的内容由程序员控制。

new+delete与molloc+free的区别

1、new、delete是C++中的操作符,而malloc和free是标准库函数。

2、对于非内部数据对象来说,只使用malloc是无法完成动态对象要求的,一般在创建对象时需要调用构造函数,对象消亡时,自动的调用析构函数。而malloc free是库函数而不是运算符,不在编译器控制范围之内,不能够自动调用构造函数和析构函数。而NEW在为对象申请分配内存空间时,可以自动调用构造函数,同时也可以完成对对象的初始化。同理,delete也可以自动调用析构函数。而mallloc只是做一件事,只是为变量分配了内存,同理,free也只是释放变量的内存。

3、new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而 malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。

placement new(c++11以后的新特性)

listlink *a=new (p)listlink(); 在p地址下分配内存给该对下

虚函数表内存如何分配

 C++中虚函数表位于只读数据段,也就是C++内存模型中的常量区;而虚函数则位于代码段,也就是C++内存模型中的代码区。

虚函数表在程序编译时就确定了,每个虚类都有个虚函数表,实例化对象的时候会生成该对象的指向虚函数表的指针,而模板是运行时确定也就是需要且没有的时候实例化一下

类内模板函数可以是虚函数么?

不可以,eg:每个类的虚函数如果有n个,就需要n大小空间存放虚函数表,

     如果允许一个成员模板函数为虚函数的话,因为我们可以为该成员模板函数实例化出很多不同的版本,也就是可以实例化出很多不同版本的虚函数,那么编译器为了确定类的虚函数表的大小,就必须要知道我们一共为该成员模板函数实例化了多少个不同版本的虚函数。显然编译器需要查找所有的代码文件,才能够知道到底有几个虚函数,这对于多文件的项目来说,代价是非常高的,所以规定成员模板函数不能够为虚函数。

c++ 类模板函数模板的问题,模板函数不能是虚函数,虚函数不能是内联 - 大老虎打老虎 - 博客园

delete与delete[]的区别

一个是释放指针,一个释放指针数组,但对于基本类型而言,实测是没差别的,只有对自定义类对象分配内存空间时,第一个就错了

内存对齐(这个百度,有例子会理解的容易些,我懒得写)

内联函数和宏定义的区别

虽然两者都是替换,但是宏定义编译器不会检查,而且内联函数本质上还是函数只不过在调用的时候讲代码部分原地展开,不占用栈资源,提高运行效率

Static的用法?

 静态局部变量:作用域只在函数内部,但是在全局静态存储区分配内存,也就是说生存周期随着程序运行结束而结束。会在第一次被执行的时候初始化,以后的函数调用不再初始化。

 全局静态变量/static修饰的函数:隐藏。该变量/函数不能被其他文件引用。

  静态数据成员:只会被初始化一次,与实例无关,并且只能在类外初始化。存储在全局静态区。

  静态成员函数:用于修饰类的成员函数。只能访问静态数据成员或静态成员函数。

Const的用法?

  const修饰变量:存储在常量区,不允许被修改。修饰指针变量分为指向常量的指针和指针本身是常量。如果修饰的是类的成员变量那么必须在类中定义的时候初始化或者在构造函数的初始化列表中初始化。

  const修饰函数:如果修饰返回值表示返回值不可被修改。如果修饰成员函数表示不可在函数中修改任何成员变量。

  const修饰对象:表示这个对象是一个常量,在初始化的时候要对所有成员变量都初始化。共有变量只能读,并且只能调用const成员函数。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

这种东西问的其实很没意思的

如何判断电脑是大端还是小端?

int main() 
{
    int a=48;
    char *ch=(char*)(&a);
    cout<<ch[0]<<endl;
    if(ch[0]=='0') cout<<"小端"<<endl;
    else cout<<"大端"<<endl;
}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

C++11以后的新特性

C++中四种类型转换是:static_cast, dynamic_cast, const_cast, reinterpret_cast

1、const_cast

用于将const变量转为非const

2、static_cast

用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;

3、dynamic_cast

用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。

向上转换:指的是子类向基类的转换

向下转换:指的是基类向子类的转换

它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。

4、reinterpret_cast

几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

5、为什么不使用C的强制转换?

C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。

智能指针

智能指针的作用是管理一个指针,使用指针有时会出现申请的空间在函数结束时忘记释放,造成了内存泄漏,使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,超出了类的作用域,类会自动调用析构函数,析构函数会自动释放创建时申请的内存空间。

三种智能指针(auto_ptr在11以后被弃用了)

unique_ptr 一个指针对应一个对象

shared_ptr 多个指针对应一个对象(有个size内置变量记录数量size为0时释放空间)

weak_ptr (弱引用指针,为了防止两个shared指针含有对方的一个对象,互相引用,就是造成size永不为0的情况而特意引入的)

shared_ptr是线程安全的么?

自带读写锁机制(追问:还有其他方法么?)

只要是能使线程同步的都可以

手写shared_ptr和weak_ptr (这个写的有问题啊,不够细啊) TODO(下次一定写个详细的)

#include<bits/stdc++.h>
using namespace std;
template<typename T>
class SharedPtr
{
public:
        SharedPtr(T *ptr=NULL)
               :_ptr(ptr)
               , _pcount(new int(1))
        {}
        SharedPtr(const SharedPtr&s)
               :_ptr(s._ptr)
               , _pcount(s._pcount)
        {
               *(_pcount)++;
        }


        ~SharedPtr()
        {
               --(*(this->_pcount));
               if (this->_pcount == 0)
               {
                       delete _ptr;
                       _ptr = NULL;
                       delete _pcount;
                       _pcount = NULL;
               }
        }
private:
        T*_ptr;
        int *_pcount;//指向引用计数的指针
};
int main()
{
        SharedPtr<int>p1(new int(1));
        SharedPtr<int>p2(p1);

        system("pause");
        return 0;
}

C++迭代器如何实现的?

C++ STL迭代器原理和实现 - wengle - 博客园

左值引用和右值引用

其实就是一个等式左边的就是左值右边的就是右值

(上面这样说也没毛病)

带有地址的都是左值,没有的就是右值

  • 可以取地址的,有名字的,非临时的就是左值;
  • 不能取地址的,没有名字的,临时的就是右值;

右值引用主要用于移动资源(不用右值引用的话就是把资源复制一遍过去,有了的话就可以直接转移资源,少了一步复制,其实左值引用的目的也是这个,函数传递参数是用左值就少了复制)

上段话可以这样理解:

#include <iostream>
using namespace std;

class Copyable {
public:
    Copyable(){}
    Copyable(const Copyable &o) {
        cout << "Copied" << endl;
    }
};
Copyable ReturnRvalue() {
    return Copyable(); //返回一个临时对象
}
void AcceptVal(Copyable a) {

}
void AcceptRef(const Copyable& a) {

}

int main() {
    cout << "pass by value: " << endl;
    AcceptVal(ReturnRvalue()); // 应该调用两次拷贝构造函数
    cout << "pass by reference: " << endl;
    AcceptRef(ReturnRvalue()); //应该只调用一次拷贝构造函数
}

一次构造函数都没用,因为编译器发现你传入一个临时变量,又创建了一个临时变量把值返回,他就会自动选择将原本的临时变量拿来用,免除拷贝的时间

移动语义和完美转发:不是拷贝对象放进来,而是将指针指向原资源

很简单的例子,一个字符串你用深拷贝就是新开辟空间,复制值,新建指针指向该空间,函数运行完释放,用移动就是将原有资源给了新值,那原有资源指向哪里呢?为空,(这是明抢啊)

其实这块有个很典型的应用场景

就是函数传参,参数类型为unique_ptr的时候,就可以传std::move(unique_ptr)这样,这其实就是移动语义了,因为unique_ptr不能拷贝

右值引用的典型代表  move,将左值当右值使用移动构造函数

话外题:为什么拷贝构造函数一定要传引用呢?

因为传值的话要复制,就会急需调用拷贝构造函数

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值