【C++11】final与override关键字和类的新功能

简介: #「持之以恒」挑战赛-30天技术创作养成记!#目录一、final与override关键字1.1 final 1.2 override二、类的新功能2.1 默认成员函数2.2 类成员变量初始化2.3 default关键字2.4 delete关键字

#「持之以恒」挑战赛-30天技术创作养成记!#

目录

一、final与override关键字

1.1 final

1.2 override

二、类的新功能

2.1 默认成员函数

2.2 类成员变量初始化

2.3 default关键字

2.4 delete关键字


注意:C++专栏的所有测试代码都是在 vs2019 的环境下编译运行的

一、final与override关键字

这两个关键字用于继承和多态

1.1 final

final:修饰虚函数,表示该虚函数不能再被重写

测试代码

//基类classPerson {
public:
//被final修饰,该虚函数不能再被重写virtualvoidBuyTicket() final    { 
cout<<"Person-买票-全价"<<endl; 
    }
};
//派生类classStudent : publicPerson {
public:
//派生类的虚函数重写了父类的虚函数virtualvoidBuyTicket() { cout<<"Student-买票-半价"<<endl; }
};

编译直接报错

image.png

final 关键字也可用于实现一个不能被继承的类

如何实现一个不能被继承的类??

  1. 把构造函数进行私有,这是 C++98 的做法
  2. 类定义时 加 final 关键字,这是 C++11的做法

测试代码

//基类使用 final 修饰classAfinal{};
//派生类无法进行继承基类classB : publicA{};

编译直接报错

image.png

1.2 override

override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

测试代码

//基类classPerson {
public:
//基类的虚函数virtualvoidBuyTicket() { cout<<"Person-买票-全价"<<endl; }
};
//派生类classStudent : publicPerson {
public:
//派生类完成了虚函数的重写,编译通过virtualvoidBuyTicket()override    { 
cout<<"Student-买票-半价"<<endl; 
    }
};
//派生类classSoldier : publicPerson{
public:
//派生类没有完成虚函数的重写,编译报错virtualvoidBuyTicket(intn)override    { 
cout<<"Soldier-优先-买票"<<endl;
    }
};

编译直接报错

image.png

二、类的新功能

2.1 默认成员函数

在C++11之前,一个类中有如下六个默认成员函数

  • 1. 构造函数
  • 2. 析构函数
  • 3. 拷贝构造函数
  • 4. 拷贝赋值重载
  • 5. 取地址重载
  • 6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的

C++11 新增了两个:移动构造函数和移动赋值运算符重载

默认移动构造的生成条件

如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。

移动构造的默认行为:

  • 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造

默认移动赋值的生成条件

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。

移动赋值的默认行为:  

默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋

值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

注意如果提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值

测试代码,需要使用简化版模拟实现的string

namespacefy{
classstring    {
public:
//构造函数string(constchar*str="")
        {
_size=strlen(str);//字符串大小_capacity=_size;//构造时,容量大小默认与字符串大小相同_str=newchar[_capacity+1];//为字符串开辟空间(多开一个用于存放'\0')strcpy(_str, str);//将C字符串拷贝到已开好的空间        }   
//拷贝构造 -- 现代写法string(conststring&s)
            :_str(nullptr)
            , _size(0)
            ,_capacity(0)
        {
cout<<"string(const string& s) -- 深拷贝"<<endl;
stringtmp(s._str);//复用构造函数,构造 tmp对象swap(tmp);//交换        }
//赋值重载 -- 现代写法1string&operator=(conststring&s)
        {
cout<<"string& operator=(const string& s) -- 深拷贝"<<endl;
if (this==&s)//检查自我赋值            {
return*this;
            }
stringtmp(s);//复用拷贝构造函数,用s拷贝构造出对象tmpswap(tmp);
return*this;//返回左值,目的是为了支持连续赋值        }
// 移动构造string(string&&s)
            :_str(nullptr)
            , _size(0)
            , _capacity(0)
        {
cout<<"string(string&& s) -- 移动语义"<<endl;//更明显观察是否调用了该函数swap(s);//与右值的资源进行直接交换,不进行深拷贝        }
// 移动赋值string&operator=(string&&s)
        {
cout<<"string& operator=(string&& s) -- 移动语义"<<endl;//使更明显观察是否调用了该函数swap(s);//与右值的资源进行直接交换,不进行深拷贝return*this;//支持连续赋值        }
//析构函数~string()
        {
delete[] _str; //释放_str指向的空间_str=nullptr;
_size=_capacity=0;
        }
//交换两个字符串voidswap(string&s)
        {
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
        }
private:
char*_str;
size_t_size;
size_t_capacity;
    };
}

然后再编写一个简单的 Person类,该类使用了我们模拟实现的string

classPerson{
public:
//构造函数Person(constchar*name="", intage=0)
        :_name(name)
        , _age(age)
    {}
private:
fy::string_name;
int_age;
};
intmain()
{
Persons1;
Persons2=s1;
Persons3=std::move(s1);
Persons4;
s4=std::move(s2);
return0;
}

注意:这里的 Person类没有实现拷贝构造、赋值重载、析构函数,只实现了构造函数,即满足移动赋值和移动拷贝函数的生成条件

运行结果如下

image.png

默认生成的移动构造和移动赋值函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋

值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值,这里完全符合

下面进行测试

一但 Person类的拷贝构造、拷贝赋值和析构函数实现了任意一个或者多个,不满足移动赋值和移动拷贝函数的生成条件,移动赋值和移动拷贝函数就不会生成

测试代码

classPerson{
public:
//构造函数Person(constchar*name="", intage=0)
        :_name(name)
        , _age(age)
    {}
拷贝构造//Person(const Person& p)// :_name(p._name)// ,_age(p._age)//{}赋值重载//Person& operator=(const Person& p)//{//  if(this != &p)//  {//      _name = p._name;//      _age = p._age;//  }//  return *this;//}//析构函数~Person()
    {}
private:
fy::string_name;
int_age;
};
intmain()
{
Persons1;
Persons2=s1;
Persons3=std::move(s1);
Persons4;
s4=std::move(s2);
return0;
}

运行结果

image.png

由于默认的移动赋值和移动拷贝函数没有生成,此时 Person类只能调用 string的深拷贝函数,调不到移动赋值和移动构造函数

2.2 类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这里在类和对象的篇章已经谈过

classPerson{
public:
//...private:
//非静态成员变量,可以在成员声明时给缺省值string_name="张三"; //缺省值int_age=22;         //缺省值staticint_n; //静态成员变量不能给缺省值};

注意:这里不是初始化,给的的值是缺省值

2.3 default关键字

default关键字的作用是:强制生成默认函数

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成

使用:在需要强制生成默认函数的声明后面加上 =default 即可

测试代码

classPerson{
public:
//构造函数Person(constchar*name="", intage=0)
        :_name(name)
        , _age(age)
    {}
//拷贝构造Person(constPerson&p)
     :_name(p._name)
     ,_age(p._age)
    {}
//移动构造Person(Person&&p) =default;
private:
fy::string_name;
int_age;
};
intmain()
{
Persons1;
Persons2=s1;
Persons3=std::move(s1);
return0;
}

运行结果,使用了 default关键字后默认的移动构造可以生成,即便实现了拷贝构造函数

image.png

2.4 delete关键字

delete关键字的作用是:禁止生成默认函数

使用:在需要禁止生成默认函数的声明后面加上 =delete 即可

如果能想要限制某些默认函数的生成:

  • 在C++98中,是该函数设置成private,并且只用声明不用定义,这样只要其他人想要调用就会报错。
  • 在C++11中更简单,只需在该函数声明加上 =delete 即可,该语法指示编译器不生成对应函数的默认版本,称 =delete 修饰的函数为删除

测试代码:

要让一个类不能被拷贝,可以用 =delete 修饰将该类的拷贝构造和赋值重载

classA{
public:
A()
    {}
private:
//强制不允许生成A(constA&) =delete;
A&operator=(constA&) =delete;
};
intmain()
{
Aa1;
Aa2(a1);
return0;
}

编译报错

image.png

----------------我是分割线---------------

文章到这里就结束了,下一篇即将更新

相关文章
|
5天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
25 5
|
11天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
40 4
|
12天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
36 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
24 4
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
1月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
53 1
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
20 1