防卫式声明,防止头文件重复引用带来的错误
#ifndef __COMPLEX__ #define __COMPLEX__ class complex{}; #endif
构造函数是特殊的类的成员函数,用于控制类的对象的初始化
构造函数没有返回类型,不同构造函数的参数数量或类型必须有区别
构造函数不能声明为const的
,因此如果创建类的const对象时,构造过程中构造函数可以向其写值,知道完成初始化过程,对象才真正拥有常量属性
无需实参,用于防止自定义类型的对象未初始化
只有类没有声明其它任何构造函数时,编译器才会自动生成默认构造函数
默认构造函数的初始化规则:
(1)用类内的初始值来初始化成员:初始值放在{}中,或=右边,但是不能用()
(2)默认初始化:由变量类型决定默认值(外加定义变量的位置的影响),内置类型定义于任何函数体之外的变量被初始化为0,定义在函数体内部的内置类型变量将不被初始化,未被初始化的内置类型变量的值是未定义的
如果类的对象没有被显式的初始化,则值由类决定
如果类中包含一个其他类类型的成员,且这个成员的类型没有默认构造函数,则编译器会无法初始化该成员,所以我们必须自定义默认构造函数
Sales_data total;
定义了其他构造函数,则必须定义一个默认构造函数
//定义默认构造函数 Sales_data() = default;
如果没有提供类内初始值,则应该在默认构造函数中使用构造函数初始值列表
comlpex(double r,double i):re{r},im{i} {} //如果re有类内初始值0.0,那么下面的两个构造函数等价 complex(double i):re{0.0},im{i} {} complex(double i):im{i} {}
一般如果构造函数执行一些实际的操作时,定义在类外部
//要指明构造函数是哪个类的成员 Sales_data::Sales_data(std::istream &is){ read(is,*this); }//*this为this对象,this是一个Sales_data对象的引用
构造函数的第一个参数是自身类类型的引用,其他任何参数都有默认值
拷贝构造函数一般不应该是explicit的
对象发生拷贝的几种情况:1. 使用 = 定义变量 2. 以值的方式返回或传递一个对象
注意注意:C++ primer中描述到:很多需要动态内存的类能够使用vector对象或string对象管理必要的存储控件,也就是如果类中包含这两种成员,其拷贝,赋值,销毁的合成版本可以正常工作
对于某些类我们不能直接使用编译器为我们自动合成的拷贝赋值销毁操作
比如类中带指针时:
class String{ public: String(const char* cstr = 0); String(const String &str);//拷贝构造,特殊的点在于,它接收的参数是String类的 String&operator = (const String &str);//操作符重载,特殊点:赋值符右边也是String ~String(); char* get_c_str()const {return m_data;}//返回一个指针 private: char* m_data; };
一般来说只要类中带有指针,就要考虑不适用默认的拷贝构造和拷贝赋值,自己来写
而且一定要写析构函数
上面的代码中的构造函数有传入C风格字符串的版本:char* str;指针指向头,最后会有'\0'代表结束符号
拷贝赋值函数,析构函数主要会写在第13章的笔记中 看P441 P266
默认的访问权限是使用class和struct定义类的唯一区别
public:成员在整个程序中可被访问(用于定义接口)
private:可以被类的成员访问,不能被使用该类的代码访问
class Sales_data { public: Sales_data() = default; Sales_data(const string&s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){} Sales_data(istream&); string isbn() const { return bookNo; } Sales_data& combine(const Sales_data&); private: double avg_price()const { return units_sold ? revenue/units_sold:0; } string bookNo; unsigned units_sold = 0; double revenue = 0.0; };
让其他类或函数成功friend,就可以访问类的非公有成员
方法是做出友元声明:
class Sales_data { //做为非成员函数出友元声明 friend Sales_data add(const Sales_data&,const Sales_data&); public: Sales_data() = default; Sales_data(const string&s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){} Sales_data(istream&); string isbn() const { return bookNo; } Sales_data& combine(const Sales_data&); private: double avg_price()const { return units_sold ? revenue/units_sold:0; } string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; Sales_data add(const Sales_data&,const Sales_data&){}
注意:友元声明只能出现在类定义的内部,但是在内部出现的位置不限,因为不是类成员所以不受访问控制的约束
友元需要在类内的友元声明之外专门对函数进行独立的声明
类中可以自定义类类型的别名
类型成员通常出现在类开始的地方
代码中的get有两个版本
希望修改类中的数据成员,在const成员函数内也可以在变量声明中加入mutable关键字做到
可变数据成员永远不会是const,即使它是const对象的数据成员
class Screen{ public: void some_member()const; private: mutable size_t access_ctr = 0;//用于记录成员函数被调次数 }; void Screen::some_member() const { ++access_ctr; }
类类型的数据成员:
提供类内初始值时,必须以 = ,{}表示:
// = 用于初始化Screen数据成员 // {}用于初始化Screen class Window_mgr { private: vector<Screen>screens{Screen(24,80,'')}; };
set返回的是调用set的对象的引用
返回引用的函数是左值的,所以set返回的是对象本身而非对象的副本
如果返回值不是引用类型,则返回值就是对象的副本,无法改变元对象的数据成员
class Screen { Screen &set(char); Screen &set(pos,pos,char); }; inline Screen &Screen::set(char c) { contents[cursor] = c;//设置当前光标所在位置的新值 return *this; } inline Screen &Screen::set(pos r,pos col,char ch) { contents[r*width+col] = ch; return *this;//设置给定位置的新值 } //调用:可以改变myscreen的成员 myscreen.move(4,0).set('#');
一个const成员函数以引用的形式返回*this,它的返回类型将是常量引用**
**
this将是一个指向const的指针,*this是const对象,所以就不能对返回值调用set等函数
常量对象不能调用非常量函数,需要调用const成员函数,而非常量对象可以调用两个版本(会自动匹配最合适的)
下面代码中的非常量版本display在调用do_display时,this指针会隐式的转换为指向常量的指针
Screen &display(ostream& os) { do_display(os); return *this; } const Screen &display(ostream& os)const { do_display(os); return *this; } void do_display(ostream& os) const { os<<contents; }
即使两个类的成员完全相同,它们是不同的类型
类的前向声明:
class Screen;
创建对象前,类必须被定义过
但是例外情况:一个类的成员不能是该类自己,但是一个类的名字出现后,就被认为是声明过了,但是尚未定义,因此类允许包含指向它自己类型的引用或指针
我们可以定义指向不完全类型的指针,但是不能创建不完全类型的对象
//正确: class X; class Y{ X* x; }; class X{ Y y; }; //错误: class Y; class X{ Y y; }; class X{ X* x; };
内联函数的实现是在类内部实现的,或是在函数前加inline关键字
内联函数理论上可以避免函数调用带来的开销(在函数不是特别复杂的情况下)
实际上内联只是一个向编译器的请求二一,编译器也可以忽略这个请求
在我们编写自己的类时,为了更加高效,简单的操作应该指定为内联的(构造函数等)
例如complex(复数类中的imag等函数):
class comlpex{ double re,im;//默认为private public: comlpex():re{0},im{0}{}//默认为{0,0} comlpex(double r,double i):re{r},im{i}{} comlpex(double r):re{r},im{0}{} double imag()const {return im;} }; inline int Max(int x,int y){ return (x>y)?x:y; } //内联函数将在调用点展开: //例如: cout<<Max(2,5)<<endl; //展开为: cout<<(x>y):x:y<<endl;