面经3

decltype

有点类似于auto,但是比auto的区别在于,
1.它是用来获取一个变量的类型然后就可以去定义其他变量了。

int tmp1=2;
decltype(tmp1) tmp2;//此时tmp2就是int类型
decltype(getSize()) tmp3;//此时tmp3也是int类型,因为decltype只分析其返回值是什么或者其变量类型是什么,并不考虑其是否真正有值。

2.auto会忽略const,decltype会保留
3.对引用操作,auto推断出原有类型,decltype推断出引用;
4.对解引用操作,auto推断出原有类型,decltype推断出引用;
5.auto推断时会实际执行,decltype不会执行,只做分析。总之在使用中过程中和const、引用和指针结合时需要特别小心。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DMs3VWLE-1585662394903)(en-resource://database/736:1)]

引用

成员初始化列表

initializer_list 列表初始化用花括号初始化器列表初始化一个对象,其中对应构造函数接受一个 std::initializer_list 参数.
initializer_list 使用

#include<iostream>
#include<vector>
#include<initializer_list>

template<typename T>
struct S{
	std::vector<T> v;
	S(std::initializer_list<T> l):v(l){
		std::cout<<"construct with a "<<l.size()<<" -elements"<<std::endl;
	}
	void append(std::initializer_list<T> l){
		v.insert(v.end(),l.begin(),l.end());
	}
	std::pair<const T*,std::size_t> c_arr() const{
		return {&v[0],v.size()};
	}
};

template<typename T>
void templated_fn(T){}

int main(){
	S<int> s={1,2,3,4};
	s.append({5,6});
	
	//得到vector的长度
	std::cout<<s.c_arr().second<<std::endl;
	
	for(auto n:s.v){
		std::cout<<n<<" ";
	}
	std::cout<<std::endl;
	
	for (int x:{90,91,92,93}){
		std::cout<<x<<" ";
	}
	std::cout<<std::endl;
	
	auto al={91,92,93};
	std::cout<<"size:"<<al.size()<<std::endl;
	templated_fn<std::initializer_list<int>>({1,2,3});
	templated_fn<std::vector<int>>({1,2,3});
	return 0;
}

内存分配和管理

malloc,calloc,realloc,alloca

1.malloc 用法:T *a=(T *)malloc(sizeof(T));,申请指定字节数的内存,原函数void *malloc(int size);常用的做法是和memset结合,void *memset(void *s,int c,size_t n)

2.calloc,void *calloc(size_t num,size_t size) 用法:

int i,*pData;
scanf("%d",&i);
pData=(int*)calloc(i,sizeof(int));

就是分配i个大小为int长度的空间,同时进行了初始化0.

3.realloc void *realloc(void *ptr,size_t new_size) 更改之前分配的内存空间长度。当重新申请的size较小的时候,一般就直接再原内存空间后面扩容。当size较大时系统会重新申请一个orgin_size+new_size的空间,并把原来的数据复制过去,free原来的空间; 如果size太太太大的时候会申请失败,原空间的不改变。

  1. alloca,在栈上申请内存,当程序出栈后,会自动释放内存。C99中有变长数组VLA来代替

malloc、free

char *str=(char *)malloc(100);
assert(str !=nullptr);
//检查释放分配成成功,释放内存后指针置空
free(str);
p=nullptr

new、delete
1.new\new[] 完成了两件事情,首先malloc分配了内存空间、然后调用构造函数创建了对象。
2.delete\delete[] 完成了首先调用析构函数清理的对象,然后底层上调用free来清理内存空间
3.new会自动计算需要的内存空间字节数,malloc需要自己设定


int main()
{
    T* t = new T();     // 先内存分配 ,再构造函数
    delete t;           // 先析构函数,再内存释放
    return 0;
}

括号问题
对于自定义类,
if new后面没有括号:
if 没有定义构造函数(编译器自动生成构造函数),也没有虚函数:
将不会调用自动生成的构造函数
else if ~ ,有虚函数
将会调用自动生成的构造函数
else 有构造函数
调用构造函数
else if 有括号
调用默认或者重写的构造函数

对于内置类型,
int *a=new int;这样不会将申请的int空间进行初始化,内容就是随机
int *a=new int();这样会将申请到的空间进行初始化,设为0.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SW4LJAxH-1585662394904)(en-resource://database/756:1)]

定位new 在指定内存区域上创建对象
new(place_address) type();
place_address是一个指针

delete this 合法吗?
合法,
但:必须保证 this 对象是通过 new(不是 new[]、不是 placement new、不是栈上、不是全局、不是其他对象成员)分配的
必须保证调用 delete this 的成员函数是最后一个调用 this 的成员函数必须保证成员函数的 delete this 后面没有调用 this 了
必须保证 delete this 后没有人使用了

如何只在堆上或栈上生成一个对象的类

类的建立有两种方式,第一种是静态建立 如A a,另一种是动态建立 new A。

静态建立,是编译器为对象在栈空间中分配内存,通过移动栈顶的指针来分配空间,有了空间之后就会调用构造函数,生成类对象
动态建立,是在运行过程中,使用new对象符将对象建了在堆空间上,两步走,第一步调用operator new()函数,在堆空间中寻找合适的内存并分配,第二步嗲用对象的构造函数,初始化这片内存空间。new的方法是间接的调用构造函数

1.只建立在堆上
建立在堆上,那就不能在比编译期间就调用构造函数,容易想到如果把构造函数私有那么就无法从外部调用构造函数了,但是这样会使得new的时候也无法调用构造函数;
正确做法:因为编译器管理了整个对象的生命周期,如果编译器发现析构函数无法被调用,那么编译器就不会再栈空间上为类对象分配内存。因此正确的做法就是私有化析构函数。

class A{
    public:
        A(){};
        void destory(){delete this;}//类对象使用完成后需要释放空间
    private:
        ~A(){};
};

但是这样会出现一个问题,继承的时候就没办法正常释放内存空间(正常是需要将析构函数设为virtual,然后在子类中重写,以实现多态),如果设为private那么子类就无法访问,因此需要用到protected,这样子类能访问到。

另外一个问题是,创建对象的时候我们用new,但是释放对象的时候用成员函数destory,而不是用delete.因为delete函数会调用析构函数再释放内存,可是析构函数不能被外部访问,就会出错。

class A{
    protected:
        A(){};
        ~A(){};
    public:
        static A *create(){
            return new A();
        }
        void destory(){
            delete this;
        }
};

这样就可以在堆上创建对象,同时调用destory释放内存


2.只能建立在栈上
只要new才会把对象建立在堆上,因此只要禁用new运算符就可以实现类对象只能建立在栈上。

class A{
    private:
        void *operator new(size_t){};
        void operator delete(void* ptr){};
    public:
        A(){};
        ~A(){};
};

智能指针

在c++标准库中,#include
c++98:std::auto_ptrstd::string ps (new std::string(str));

c++11:
1.shared_ptr
2.unique_ptr
3.weak_ptr
4.auto_ptr(被弃用)

Class shared_ptr 实现共享式拥有(shared ownership)概念。多个智能指针指向相同对象,该对象和其相关资源会在 “最后一个 reference 被销毁” 时被释放。为了在结构较复杂的情景中执行上述工作,标准库提供 weak_ptr、bad_weak_ptr 和 enable_shared_from_this 等辅助类
Class unique_ptr 实现独占式拥有(exclusive ownership)或严格拥有(strict ownership)概念,保证同一时间内只有一个智能指针可以指向该对象。你可以移交拥有权。它对于避免内存泄漏(resource leak)——如 new 后忘记 delete ——特别有用

shared_ptr
多个指针共享同一个对象,最后一个指针负责销毁对象释放内存

可以防范Cross-DLL问题(对象在动态链接库(DLL)中被 new 创建,却在另一个 DLL 内被 delete 销毁)、自动解除互斥锁

weak_ptr
weak_ptr 允许你共享但不拥有某对象,一旦最末一个拥有该对象的智能指针失去了所有权,任何 weak_ptr 都会自动成空(empty)。因此,在 default 和 copy 构造函数之外,weak_ptr 只提供 “接受一个 shared_ptr” 的构造函数。

可打破环状引用(cycles of references,两个其实已经没有被使用的对象彼此互指,使之看似还在 “被使用” 的状态)的问题

unique_ptr
unique_ptr 是 C++11 才开始提供的类型,是一种在异常时可以帮助避免资源泄漏的智能指针。采用独占式拥有,意味着可以确保一个对象和其相应的资源同一时间只被一个 pointer 拥有。一旦拥有着被销毁或编程 empty,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,其任何相应资源亦会被释放。

取代auto_ptr

强制类型转换

static_cast

  • 用于非多态转化
  • 不执行运行时的类型检查,而dynamic_cast会检查,因此不如它安全
  • 在数值类型转换上会比较常用,会丢失精度
  • 如果在类上进行转化,从子类转化为父类是安全的,因为父类有的子类都有;但是从父类转化为子类是不安全的,因为子类中可能有父类没有的字段和方法,这样转化没初始化可能会出问题.

子类向父类的转化是向上转化
向上转化是一种隐式转化
无法转化掉const类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UdVj2VqE-1585662394905)(en-resource://database/758:1)]

dynamic_cast

  • 多态类型的转化
  • 运行时检查状态
  • 只适用于指针和引用
  • 对于不确定的指针转化失败返回nullptr(0),不引发异常,因此可以用来判断是否转化成功
  • 多重继承下要注意歧义性问题
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DNXhs4xN-1585662394905)(en-resource://database/760:1)]
    如果要转换就要按照层次转化上去.

bad_cast
如果dynamic_cast强制转化引用类型失败会报出bad_cast异常

try{
    Circle& ref_circle=dynamic_cast<Circle&>(ref_shape);//向下转化 不安全会失败
}catch(bad_cast b){
    cout<<"Caught:"<<b.what();
}

const_cast

  • 用于删除const volatile和__unaligned特性,如将const int 转化为int.
    const是上面的两种cast无法删除的

reinterpret_cast

  • 用于位的简单重新解释
  • 滥用 reinterpret_cast 运算符可能很容易带来风险。 除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一。
  • 允许将任何指针转换为任何其他指针类型(如 char* 到 int* 或 One_class* 到 Unrelated_class* 之类的转换,但其本身并不安全)
  • 也允许将任何整数类型转换为任何指针类型以及反向转换。
  • reinterpret_cast 运算符不能丢掉 const、volatile 或__unaligned 特性。
  • reinterpret_cast 的一个实际用途是在哈希函数中,即,通过让两个不同的值几乎不以相同的索引结尾的方式将值映射到索引。

运行时类型信息

dynamic_cast
用于多态类型转化

typeid

  • 可以在运行时确定对象类型
  • 如果要通过基类指针获得派生类数据类型,基类必须带有虚函数
  • 只能获取对象的实际类型

type_info

type_info 类描述编译器在程序中生成的类型信息。 此类的对象可以有效存储指向类型的名称的指针。 type_info 类还可存储适合比较两个类型是否相等或比较其排列顺序的编码值。 类型的编码规则和排列顺序是未指定的,并且可能因程序而异。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值