C++ 类:类相关的非成员函数、构造函数; =delete ; =defalute;
2026/7/23 2:18:15 网站建设 项目流程

C++ 类:类相关的非成员函数、构造函数

Sales_data.h

#include<iostream>#include<string>usingnamespacestd;// Sales_data.hstructSales_data{// public部分,对象看起来是怎么样的。std::stringisbn()const{returnbookNo;}// 返回 isbn 编号Sales_data&combine(constSales_data&);// 模拟 += 运算doubleavg_price()const;// private部分,数据成员。std::string bookNo;unsignedunits_sold=0;// 表示某书的销量doublerevenue=0.0;// 表示某书的总收入};// Sales_data的非成员接口函数Sales_dataadd(constSales_data&,constSales_data&);std::ostream&print(std::ostream&,constSales_data&);std::istream&read(std::istream&,Sales_data&);

Sales_data.cpp

// Sales_data.cpp#include"Sales_data.h"usingstd::cout;usingstd::endl;Sales_data&Sales_data::combine(constSales_data&rhs){units_sold+=rhs.units_sold;revenue+=rhs.revenue;return*this;}istream&read(istream&is,Sales_data&item){doubleprice=0;is>>item.bookNo>>item.units_sold>>price;// price 表示某书单价item.revenue=price*item.units_sold;returnis;}ostream&print(ostream&os,constSales_data&item){os<<item.isbn()<<" "<<item.units_sold<<" "<<item.revenue<<" "<<item.avg_price();returnos;}doubleSales_data::avg_price()const{if(units_sold)returnrevenue/units_sold;elsereturn0;}Sales_dataadd(constSales_data&lhs,constSales_data&rhs){Sales_data sum=lhs;sum.combine(rhs);returnsum;}

Main.cpp

// main.cpp#include<iostream>#include"test.h"usingstd::cout;usingstd::endl;intmain(){Sales_data total;if(read(cin,total)){Sales_data trans;while(read(cin,trans)){if(total.isbn()==trans.isbn())total.combine(trans);else{print(cout,total)<<endl;total=trans;}}print(cout,total)<<endl;}else{cerr<<"No data?!"<<endl;}return0;}

CmakeLists

cmake_minimum_required(VERSION3.23)project(CPP5)set(CMAKE_CXX_STANDARD11)add_executable(CPP5 main.cpp test.cpp)

从上代码可知,add, print, read 不属于类本身,从概念上讲 属于类接口的组成部分。

同样,通常把非成员函数的声明和定义分开。函数从概念是属于类,虽然不定义在类型中,也同样与类声明在同一个头文件中

一般来说,如果非成员函数是类接口的组成部分,则这些函数的声明应该与类放在同一个头文件中

编译器默认为一个类生成的默认函数

  1. 默认构造函数
  2. 默认析构函数
  3. 默认拷贝构造函数
  4. 默认赋值函数
  5. 移动构造函数
  6. 移动拷贝函数

=delete

=delete;

作用:

禁止某类函数的调用

1禁止使用编译器默认生成的函数

假如上面的几个函数中,不想使用其中某个,可以将其定义为private,或者使用=delete。

#include<iostream>usingnamespacestd;classDataOnly{public:DataOnly(){}~DataOnly(){}DataOnly(constDataOnly&rhs)=delete;//禁止使用该函数DataOnly&operator=(constDataOnly&rhs)=delete;//禁止使用该函数DataOnly(constDataOnly&&rhs){}DataOnly&operator=(DataOnly&&rhs){}};intmain(intargc,char*argv[]){DataOnly data1;DataOnlydata2(data1);// error: call to deleted constructor of 'DataOnly'DataOnly data3=data1;// error: call to deleted constructor of 'DataOnly'return0;}
2. delete 关键字可用于任何函数,不仅仅局限于类的成员函数

禁止类型转换

#include<iostream>usingnamespacestd;classDataOnly{public:voidfun(inta){}voidfun(floata)=delete;};intmain(intargc,char*argv[]){DataOnly data1;data1.fun(1);// OKdata1.fun(0.5);// error: call to member function 'fun' is ambiguousreturn0;}
classX{public:X();X(constX&)=delete;// 声明拷贝构造函数为 deleted 函数X&operator=(constX&)=delete;// 声明拷贝赋值操作符为 deleted 函数};classY{public:// 禁止使用者在堆上申请对象void*operatornew(size_t)=delete;void*operatornew[](size_t)=delete;};
3. 模板特化:在模板特例化中,可以用delete来过滤一些特定的形参类型

例如:Widget 类中声明了一个模板函数,当进行模板特化时,要求禁止参数为 void* 的函数调用。
\1. 按照私有不实现思路,应该是将特例化的函数声明为私有型,如下:

#include<iostream>usingnamespacestd;classWidget{public:template<typenameT>voidProcessPointer(T*ptr){cout<<typeid(T).name()<<endl;}private:voidProcessPointer(void*){cout<<"void"<<endl;}};intmain(intargc,char*argv[]){Widget w;int*ip=NULL;w.ProcessPointer(ip);void*vp=NULL;w.ProcessPointer(vp);//error: 'ProcessPointer' is a private member of 'Widget' w.ProcessPointer(vp);return0;}

注意

请注意,删除的函数是隐式内联的,这一点非常重要。删除的函数定义必须是函数的第一个声明。换句话说,以下方法是将函数声明为已删除的正确方法:

classC{public:C(C&a)=delete;};

但是以下尝试声明删除函数的方法会产生错误:

// incorrect syntax of declaring a member function as deletedclassC{public:C();};// Error, the deleted definition of function C must be the first declaration of the function.C::C()=delete;

最后,明确删除函数有什么好处?
删除特殊成员函数提供了一种更简洁的方法来防止编译器生成我们不想要的特殊成员函数。(如“禁用拷贝构造函数”示例中所示)。
删除正常成员函数或非成员函数可防止有问题的类型导致调用非预期函数(如“禁用不需要的参数转换”示例中所示)。

=default;

如果程序没有显式地为一个类定义某个特殊成员函数,而又需要用到该特殊成员函数时,编译器会隐式地为这个类生成一个默认的特殊成员函数。
例如:

classX{private:inta;};X x;//可以编译通过,编译器隐式生成默认构造函数

但是如果X 显式的自定义了非默认构造函数,却没有定义默认构造函数,下面的代码会出现编译错误

class X{public:X(inti){a=i;}private:inta;};X x;// 错误,默认构造函数 X::X()不存在

为了解决上面的问题,我们需要自定义默认构造函数,如下

class X{public:X(){};// 手动定义默认构造函数X(inti){a=i;}private:inta;};X x;//正确,默认构造函数 X::X()不存在

但是手动编写存在两个问题:1. 程序员工作量变大 2. 没有编译器自动生成的默认特殊构造函数效率高。

不能看到上面的例子中,工作量不大,就认为上面的第一个问题不存在。请看下面的例子:

class A{public:A(intii,charcc):i(ii),c(cc){}A(constA&a);private:inti;charc;};

上述代码如果要手动写出拷贝构造函数,如下

A(const A& a):i(a.i),c(a.c){}

为了解决上述的两个问题,C++ 11标准引入了一个新特性:defaulted 函数。

1.2 使用
1.2.1 使用规则

(1) defaulted 函数特性仅用于类的特殊成员函数,且该特殊成员函数没有默认参数。例如:

class X{public:intf()=default://错误, f()非特殊成员函数X(int)=default;//错误, 非特殊成员函数X(inti=1)=default;// 错误, 含有默认参数}//能够支持default的,都是编译器能够在该情形下明确要做什么(构造函数一般是逐bit拷贝)

(2) 既可以在类体里定义(inline),也可以在类体外(out-of-line)定义。例如

classX{public:X()=default;// inlineX(constX&);//X&operator=(constX&);~X()=default;// inline}X::X(constX&)=default;// out of lineX&X::operator=(constX&)=default;// out of line
1.2.2 析构函数中的使用

先看一个例子:

classX{private:intx;};classY:publicX{private:inty;};intmain(){X*x=newY;deletex;}

上面的程序有内存泄漏的问题,因为这种情况下,会调用基类X的析构函数,并非Y的析构函数。而且此时X 的析构函数是编译器隐式自动生成的。

为了解决上面的问题,我们可以手动将X 的析构函数定义为虚函数,如下:

classX{public:virtual~X(){};// 手动定义虚析构函数private:intx;};classY:publicX{private:inty;};intmain(){X*x=newY;deletex;}

同样,为了 1. 减轻程序员的编程工作量 2.函数执行效率,我们使用default特性,如下:

classX{public:virtual~X()=defaulted;// 编译器自动生成 defaulted 函数定义体private:intx;};classY:publicX{private:inty;};intmain(){X*x=newY;deletex;

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询