【C++】对象和类(构造函数、析构函数、作用域、运算符重载、友元、类型转换)

抽象和类

OOP特性:抽象、封装、数据隐藏、多态、继承和代码可重用性。C++中,用户定义类型指的是实现抽象接口的类设计。

1. 指定基本类型完成的工作:决定数据对象需要的内存数量、决定如何解释内存中的位、决定可使用数据对象执行的操作或方法。
类声明:以数据成员的方式描述数据部分,以成员函数(被称为方法)的方式描述公有接口。类方法定义:描述如何实现类成员函数

类规范:
通常,C++程序员将接口(类定义)放在头文件中,并将实现(类方法的代码)放在源代码文件中。使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数(或友元函数)来访问对象的私有成员。公有成员函数是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。防止程序直接访问数据被称为数据隐藏,将实现细节放在一起并将它们与抽象分开被称为封装。

数据隐藏是OOP主要目标之一,因此数据项通常放在私有部分,组成类接口的成员函数放在公有部分。
结构的默认访问类型是public,而类为private。

2. 实现类成员函数
两个特征:定义成员函数时,使用作用域解析运算符(::)标识函数所属的类;类方法可以访问类的private组件。
内联方法:其定义位于类声明中的函数都将自动成为内联函数,类声明通常将短小的成员函数作为内联函数。也可在类声明之外定义内联函数(类实现部分中定义函数时加inline),将内联定义放在定义类的头文件中。

在OOP中,调用成员函数被称为发送消息,因此将同样的消息发送给两个不同的对象将调用同一个方法,但该方法被用于两个不同的对象。

3. 使用类 要创建类对象,可以声明类变量,也可以使用new为类对象分配内存存储空间。

客户/服务器模型:客户是使用类的程序,类声明(包括类方法)构成了服务器。客户只能通过以公有方式定义的接口使用服务器。

类的构造函数和析构函数

构造函数

类构造函数用于构造新对象,将值赋给它们的数据成员。其名称与类名相同,且没有声明类型。构造函数的参数表示的不是类成员,而是赋给类成员的值。

显式调用构造函数:Stock food = Stock(“Furry Mason”, 50, 2.5);
隐式调用:Stock food(“Furry Mason”, 50, 2.5);
构造函数被用来创建对象,而不能通过对象来调用。

默认构造函数是在未提供显式初始值时,用来创建对象。当仅当没有定义任何构造函数时,编译器才会提供默认构造函数,为类定义了构造函数后,程序员必须为它提供默认构造函数。定义默认构造函数两种方式:

  1. 给已有构造函数的所有参数提供默认值:
    Stock (const string &co = “Error”, int n = 0, double pr = 0.0);
  2. 通过函数重载来定义一个没有参数的构造函数: Stock( );
    只能有一个默认构造函数,用户定义的默认构造函数通常给所有成员提供隐式初始值 。在设计类时,通常应提供对所有类成员作隐式初始化的默认构造函数。

析构函数

析构函数(完成清理工作),没有参数。

原型:~Stock( ); 定义:Stock::~Stock( ){ };

析构函数调用时机:

创建的对象析构函数调用时间点
静态存储类对象程序结束自动被调
自动存储类对象程序执行完代码块
通过new创建使用delete
临时对象结束对该对象的使用时

在默认情况下,将一个对象赋给同类型的另一个对象时,C++将原对象的每个数据成员的内容复制到目标对象中相应的数据成员中。

若既可以通过初始化,也可通过赋值来设置对象的值,应采用效率更高的初始化方式。在赋值语句中使用构造函数总会导致在赋值前创建一个临时对象。

C++11提供了名为std::initialize_list的类,可将其用作函数参数或方法参数的类型,这个类可表示任意长度的列表,只要所有列表项的类型都相同或可转换为相同的类型。

const成员函数保证函数不会修改调用对象(将const放在函数括号后)。
声明:void show( ) const;
定义:void Stock::show( ) const{}

这种方式声明和定义的类函数被称为const成员函数。就像应尽可能将const引用和指针用作函数形参一样,只要类方法不修改调用对象就应将其声明为const。

this指针

指向用来调用成员函数的对象(this被作为隐藏参数传递给方法) this->

一般来说,所有的类方法都将this指针设置为调用它的对象的地址。每个成员函数(包括构造函数和析构函数)都有一个this指针,this指针指向调用对象,要引用整个调用对象,则可以使用表达式 *this, 即可以将 *this 作为调用对象的别名。

对象数组

初始化对象数组的方案是,首先使用默认构造函数创建数组元素,然后花括号中的构造函数将创建临时对象,然后将临时对象的内容复制到相应的元素中。因此,要创建类对象数组,则这个类必须有默认构造函数。

类作用域

可以在不同类中使用相同的类成员名。在类中定义的名称(如类数据成员名和类成员函数名)的作用域都为整个类,作用域为整个类的名称只在该类中是已知的。所以要调用公有成员函数,必须通过对象(定义成员函数时须使用域解析运算符)。

  1. 可以用枚举为整型常量提供作用域为整个类的符号名称
private:          //这种方式声明枚举并不会创建类成员数据
      enum {Months = 12};
      double costs[Months];
  1. C++另一种在类中定义常量的方式—关键字static
private:
      static const int Months = 12;  //这将创建Months常量,其与其他静态
      double costs[Months];   //变量存储在一起,而不是存储在对象中
  1. C++11提供了一种新枚举,其枚举量的作用域为类,如:
enum class egg {Small, Medium, Large};
enum class t_shirt{Small, Medium, Large};

其作用域内枚举的底层类型为int。

  1. 一般来说,私有数据成员存储信息,公有成员函数(又称方法)提供访问数据的唯一途径。C++试图让用户定义的类型尽可能与标准类型类似,因此可以声明对象、指向对象的指针和对象数组。可以按值传递对象、将对象作为函数返回值、将一个对象赋给同类型的另一个对象。
    如希望成员函数对多个对象进行操作,可以将额外的对象作为参数传递给它。
    this指针被设置为调用对象的地址,*this是该对象的别名。
  2. 类很适合用于描述ADT(抽象数据类型)。公有成员函数接口提供了ADT描述的服务,类的私有部分和类方法的代码提供了实现,这些实现对类的客户隐藏。
  3. 定义函数时不要返回指向局部变量或临时对象(变量)的引用。函数执行完毕后,局部变量和临时变量将消失,引用将指向不存在的数据。

运算符重载

运算符重载

C++允许将运算符重载扩展到用户定义的类型。要重载运算符,需使用被称为运算符函数的特殊函数形式,格式如:
operator op(argument_list) //op必须时有效的C++运算符
例如:

class Time{
   int hours;
   int minutes;
public:
   Time operator+(const Time & t) const
   ……
};
Time Time::operator+(const Time & t) const
{
   Time sum;
   sum.hours=hours+t.hours +;
   ……
   return sum;
}
//在测试程序中有两种表示法调用operator+( )方法:
Time coding(2,40);
Time fixing(5,55);
total = coding.operator+(fixing); //函数表示法
total = coding + fixing; //运算符表示法

在运算符表示法中,运算符左侧的对象(coding)是调用对象,运算符右边的对象(fixing)是作为参数被传递的对象。

重载限制

  1. 重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。
  2. 使用运算符时不能违反运算符原来的句法规则,不能修改运算符的优先级。
  3. 不能创建新运算符。
  4. 大多运算符都可通过成员或非成员函数进行重载,但赋值运算符‘=’、函数调用‘()’、下标‘[]’、通过指针访问类成员‘->’运算符只能通过成员运算符进行重载。

友元(友元函数、友元类、友元成员函数)

友元函数的原型在类声明中,并在原型声明前加关键字friend
friend Time operator * (double m, const Time & t);
operator * ()虽在类中声明,但它不是成员函数,因此不能使用成员运算符来调用,但它与成员函数的访问权限相同。

不要在定义函数时使用friend,类的友元函数不是成员函数,其访问权限与成员函数相同。

应将友元函数看作类的扩展接口的组成部分。类方法和友元函数只是表达类接口的两种不同机制。如果要为类重载运算符,并将非类的项作为其第一个操作数,则可以使用友元函数来反转操作数的顺序。

常用的友元:重载<<运算符
cout是一个ostream对象,能识别所有C++基本类型,这是因为对于每种基本类型,ostream类声明中都包含了相应的重载的operator<<()定义。ostream类将operator<<()函数实现返回一个指向ostream对象的引用。

cout<<x<<y; 等同于(cout<<x)<<y;

只有在类声明中的原型才能使用friend关键字,除非函数定义也是原型,否则不能在函数定义中使用它。

对于很多运算符来说,可选择使用成员函数或非成员函数来实现运算符重载。一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据。非成员版本的重载运算符函数所需的形参数目与运算符使用的操作数数目相同;而成员版本所需的参数数目少一个,因为其中的一个操作数是被隐式地传递的调用对象。(this指针)

状态成员:描述对象所处的状态。如果方法通过计算得到一个新的类对象,则应考虑是否可以使用类构造函数来完成。

头文件cstdlib包含了srand( )rand( )的原型,ctime包含了time( )的原型。time(0)返回程序执行到当前的时间,以秒级。srand( )函数允许覆盖默认的种子值,重新启动另一个随机数序列。使用语句srand(time(0))可使rand( )产生更随机的随机数(再次运行程序时结果会不一样)。C++使用头文件radom中函数提供更强大的随机数支持。

类的自动类型转换和强制类型转换

只接受一个参数的构造函数定义了从参数类型到类类型的转换。若使用关键字explicit限定了这种构造函数,则它只能用于显式转换,否则也可以用于隐式转换。

转换函数

构造函数只用于从某种类型到类类型的转换;转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们。
创建转换函数,转换为typeName类型:operator typeName();注意:转换函数必须是类方法(通过类对象调用),不能指定返回类型也不能有参数。

例:

class Stonewt
{
    ……
    double pounds;
public:
    ……
    operator int( ) const;
};
Stonewt::operator int ( ) const
{
    return int(pounds+0.5); //int转换将待转换的值四舍五入为最接近的整数,
}     /114.4(+0.5)->114.9->114
int main()
{
    …… //定义类对象c中pounds为128.8
    cout<<int(c)<<endl; //输出为129
}

原则上最好使用显式转换,避免隐式转换

总之,C++为类提供了下面的类型转换

  1. 只有一个参数的类构造函数用于将类型与该参数相同的值转换为类类型。例如,将int值赋给Stonewt对象时,接受int参数的Stonewt类构造函数将自动被调用。在构造函数声明中使用explicit可防止隐式转换,而只允许显式转换。
  2. 被称为转换函数的特殊类成员运算符函数,用于将类对象转换为其它类型。转换函数是类成员,没有返回类型,没有参数,名为operator typeName(),其中typeName是对象将被转换的类型。将类对象赋给typeName变量或将其强制转换为typeName类型时,该转换函数将自动被调用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
自己整理的C++要背的一些概念,华南师范大学考c++的建议看看,21届考研有考到哦!!(可打印版) 1. 面向对象的三个基本特征 4 2. 抽象 4 3. 如何实现多态性 4 4. const与define的区别 4 5. static作用 4 6. 静态数据成员与静态成员函数 5 7. 静态数据成员与全局变量的不同 5 8. 为什么引入友元的概念? 5 9. 什么是友元函数? 5 10. 友元成员函数 5 11. 友元 5 12. 函数重载 5 13. 运算符重载 6 14. 运算符重载函数的使用 6 15. 重载运算符的规则 6 16. 为什么要使用继承 6 17. 保护成员的作用 6 18. 多重继承 6 19. 虚基及其作用 6 20. 派生构造函数析构函数的构造规则 7 21. 虚函数及其作用 7 22. 静态关联和动态关联 7 23. 函数重载与虚函数的不同 7 24. 虚析构函数 8 25. 纯虚函数 8 26. 抽象 8 27. 抽象与接口的区别 8 28. 32.输入输出流 8 29. 标准的输出流对象有哪些 8 30. 标准的输入流对象有哪些 9 31. 文件流与文件流对象 9 32. 两个短整型数相加后,结果是什么型? 9 33. 什么是值传递? 9 34. 值传递、引用传递和指针传递的区别是什么? 9 35. 什么是函数模板?什么是模板函数?函数模板有什么用途? 9 36. C++是如何实现函数重载的? 10 37. 全局变量和局部变量的主要区别是什么? 使用全局变量有什么好处?有什么坏处? 10 38. 变量定义和变量声明有什么区别? 10 39. 引入结构体有什么好处? 10 40. 单链表中为什么要引入头结点? 10 41. 结构体型定义的作用是什么? 结构体型的变量定义的作用是什么? 10 42. 用自己的话描述逐步细化的过程 10 43. 为什么库的实现文件要包含自己的头文件? 11 44. 什么头文件要包含 #ifndef...#endif这对编译预处理指令? 11 45. 为什么要使用库? 11 46. 用struct定义型与用class定义型有什么区别? 11 47. 构造函数析构函数的作用是什么?它们各有什么特征? 11 48. 友元的作用是什么? 11 49. 静态数据成员有什么特征?有什么用途? 11 50. 在定义一个时,哪些部分应放在头文件(.h文件)中,哪些部分应放在实现文件(.cpp文件)中? 12 51. 什么情况下必须定义自己的复制构造函数? 12 52. 常量数据成员和静态常量数据成员有什么区别?如何初始化常量数据成员?如何初始化静态常量数据成员? 12 53. 什么是this指针?为什么要有this指针? 12 54. 复制构造函数的参数为什么一定要用引用传递,而不能用值传递? 12 55. 构造函数为什么要有初始化列表? 12 56. 如何区分++和--的前缀用法和后缀用法的重载函数? 12 57. 有几个默认的成员函数? 13 58. 什么是抽象?定义抽象有什么意义? 抽象在使用上有什么限制? 13 59. 为什么要定义虚析构函数? 13 60. 试说明派生对象的构造和析构次序。 13 61. 试说明虚函数和纯虚函数有什么区别 13 62. 基指针可以指向派生对象, 为什么派生的指针不能指向基对象? 13 63. 如果一个派生新增加的数据成员中有一个对象成员,试描述派生的构造过程 13 64. 为什么函数模板的使用与普通的函数完全一样, 而模板在使用时还必须被实例化? 13 65. 模板继承时的语法与普通的继承有什么不同? 13 66. 什么是打开文件?什么是关闭文件?为什么需要打开和关闭文件? 14 67. 为什么要检查文件打开是否成功?如何检查? 14 68. ASCII文件和二进制文件有什么不同? 14 69. C++有哪4个预定义的流? 14 70. 什么时候用输入方式打开文件?什么时候应该用输出方式打开文件?什么时候该用app方式打开文件? 14 71. 哪些流操纵符只对下一次输入/输出有效?哪些流操纵符是一直有效直到被改变? 14 72. 什么叫做作用域?什么叫做局部变量? 什么叫做全局变量? 14

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhugenmi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值