类
类函数定义和类声明可以放在不同文件中
类对象默认private
作用域解析运算符表示所属的类
void Stock::update(double price)
定义于类声明中的函数将自动成为内联函数,在类外定义则需要显式inline关键字
不能将类成员名字作为构造函数的参数名字可以在前面加上“m"或后面加上”\"
默认构造函数和函数调用的区别:
Stock stock; //隐式调用默认构造函数
Stock stock = Stock();//显式调用
Stock * p = new Stock; //隐式调用默认构造函数
Stock first("aa");
Stock sec();//函数声明
Stock third;//隐式调用
隐式调用默认构造函数不要使用圆括号
析构函数~Stock()
通常不要在代码中显式调用
如果是静态储存类对象,析构函数在程序执行结束时被调用;如果是自动储存类对象,析构函数在程序执行完代码块被调用;如果是new创建的,则将在delete时被调用
差别:
Stock stock1 = Stock("a");
stock1 = Stock("b");
1是初始化,可能会创建临时对象;2是赋值,总会导致先创造一个临时对象。因此尽可能使用初始化方式
c++列表初始化用于类,与构造函数参数匹配就行
const 成员函数:
void show() const;
只要类方法不修改调用对象,就应该使用const
特殊:接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值, explict关键字可以关闭这个功能
Classname object = value
C(a);新的显式转换
(C)a;老式的
默认构造函数可以没有任何值,如果有则必须为每个参数提供默认值
this指针
对象数组:类必须有默认构造函数
Stock stocks[3] = {
Stock(1),
Stock(1),
Stock(1),
}
首先默认构造函数创建数组元素,然后大括号中构造临时对象,赋值
作用域为类的常量
-
枚举
class B { private: enum {M = 12}; }
-
static const
c++11类枚举:
enum class egg {S, M, L};
egg::L;
只能通过成员函数重载 | |
---|---|
= | 赋值 |
() | 函数调用 |
[] | 下标 |
-> | 指针访问 |
友元:
- 友元函数 原型放在类声明中,前面加上关键字friend,定义中不加关键字,不加限定符
- 不是成员函数,不能通过成员运算符调用
- 访问权限和成员函数相同
- 友元类
- 友元成员函数
A = B.operator*(1);
A = operator*(1, B);
Time operator*(int i, const Time & t);
某个类要使用cout必须使用友元重载
void operator<<(ostream & os, const Time & t)
{
os << t.time;
}
//该函数不必是ostream的友元,因为没有直接访问ostream的成员,但必须是Time的友元
//优化
ostream & operator<<(ostream & os, const Time & t)
{
os << t.time;
return os;
}
重载运算符:
对于成员函数版本来说,一个操作数通过this指针隐式传递,一个操作数通过函数参数显式传递;
对于友元版本来说,两个都通过参数传递
T1 = T2 + T3;
//1
T1 = T2.operator+(T3);
T1 = operator+(T2, T3);
cstdlib
ctime
c++:random头文件
rand()
srand(time(0));
转换函数:operator typeName()
- 必须是类方法
- 不能指定返回值
- 不能有参数
[explict] operator int() const;// 原型 加上关键字后必须显式转换
//定义
S::operator int() const
{
return int (a);
}
自动应用类型转换:不能有歧义
谨慎使用隐式转换函数
将加法定义为友元函数可以让程序更容易适应自动类型转换
将double和Class相加:
-
友元函数方式定义:
operator+(const Class &, const Class &)
,之后让Class(double)构造函数自动将double类型的参数转换成Class类型的 -
直接重载为显式使用double类型参数的函数 可以有两种完全匹配的方式,double在前将与友元匹配,在后将与成员函数匹配
Class operator+(double x)
friend Class operator+(double x, Class & s)
方法一依赖于隐式转换,代码量少,缺点是每次转换都要调用构造函数,开销大;方法二速度快
如果经常要将double值与Class相加,方法2合适,如果只是偶尔使用,方法1合适,保险起见可以使用显式转换
类和动态内存分配
不能在类声明中初始化静态成员变量, 初始化时不用static关键字
初始化在方法文件中,不在类声明文件中(头文件),避免出现多个初始化语句副本
一种例外:静态数据成员为整型或枚举型const
当使用对象初始化来构造另一个对象时,编译器将自动生成复制构造函数
String sailor = sports
等效于:
String sailor = String(sports)
复制构造函数原型是:
String(const String &)
何时调用
新建一个对象并将其初始化为同类现有对象
S b(a);
S c = a;
S d = S(a);
S * e = new S(a);//初始化一个匿名对象,地址赋给指针
2和3可能会使用复制构造函数直接生成也有可能生成一个临时对象之后赋给。
函数按值传递对象或函数返回对象时将使用复制构造函数
c++自动提供以下成员函数,如果没有定义:
-
默认构造函数
-
默认析构函数
-
复制构造函数
默认逐个复制非静态成员, 浅复制;如果对象使用了自己分配的内存,就要使用深复制,同时可能更新所有受影响的静态成员类
-
赋值运算符
ANSI C允许结构赋值,c++允许类对象赋值,通过自动给类重载赋值运算符
C & C::operator=(const C &)
将已有对象赋值给另一个对象时调用,初始化对象时不一定调用,比如:
S m = t
,调用了复制构造运算符,但是有可能生成一个临时对象再赋值重新定义赋值运算符注意点:
- delete[]
- 避免赋值给自身
- 返回一个指向调用对象的引用,允许连续赋值 s1=s2=s3;
S & S::operator=(const S & st) { if (this == & st) return *this; delete [] str; len = st.len; str = new char [len+1]; std::strcpy(str, st.str); return *this; } //检查自我赋值的情况 //释放以前的内存 //赋值数据内容 //返回调用对象的引用
-
地址运算符
重载下标运算符:
char & String::operator[](int i)
{
return str[i];
}
//如果:
const String answer("AAA");
cout << answer[2];//报错
// 添加一个专门给常量对象使用的重载函数
//重载时,c++将区分常量和非常量函数的特征标
const char & String::operator[](int i) const
{
return str[i];
}
静态类成员函数:不能通过对象调用,只能使用静态数据成员
进一步重载赋值运算符:
String name;
char temp[10];
cin.getline(temp,10);
name = temp; //use constructor to convert type;
使用了构造函数String(const char *)
创建一个临时对象,在使用赋值运算符将临时对象中的信息赋值到name中,最后调用临时对象的析构函数
提高效率:重载赋值运算符:
String & String::operator=(const char * s)
{
delete [] str;
len = std::strlen(s);
str = new char [len+1];
std::strcpy(str, s);
return *this;
}
返回引用对象:
- 不会调用复制构造函数
- 引用指向的对象应该在调用函数执行后存在
- 如果源对象是const则返回的引用也要是const
问题:
net = val1 + val2;
val1 + val2 = net;//合法?
//返回const引用对象可解决该问题
析构函数的调用细节:
- 对象是动态变量,则执行完定义该对象的程序块时调用
- 静态变量:程序结束时
- new :手动delete
定位new运算符:不能和delete配合 只能用delete[]释放整个内存块,但是那些内存块里的对象持有的内存怎么办?只能给这些对象显式调用析构函数
从晚到早去调用析构函数,最后删除buffer块
成员初始化列表,不局限于初始化常量
Queue::Queue(int qs) : qsize(qs) //常量类成员在这里初始化
{
...
qsize = qs;//error
}
被声明为引用的类成员也必须用初始化列表,引用与const类似,只能在被创建的时候初始化,放到构造函数内的话,变量内存空间都分配完了。对于基本类型没什么区别,对于本身就是类对象的成员来说,初始化列表效率更高。
class A {...};
class AA {
private:
A & belong;
}
AA:AA(A & a) : belong(a) {...}
C++11类内初始化和在构造函数中使用初始化列表等价,使用成员初始化列表的构造函数将覆盖相应的类内初始化
派生:
class SubClass : public SuperClass
{
}
SubClass::SubClass(int i) : SuperClass(i)
{
...
}
写派生类的构造函数时,必须用到基类的构造函数,默认调用不带参数的版本。基类对象在派生类对象之前被创建。
基类类型的指针可以在不进行显示转换的情况下指向派生类对象,基类引用可以在不进行显式转换的情况下指向派生类对象
多态公有继承:
- 派生类中重新定义基类方法
- 虚方法
virtual关键字:
如果没有使用根据引用类型或指针类型选择方法,没有多态了,否则根据实际对象类型选择方法
在派生类方法中调用基类方法要用域解析运算符,如果没有重新定义就不用域解析运算符了,因为知道肯定是基类方法
虚析构函数:如果不是虚的则只会调用对应指针类型的析构函数
如果要在派生类中重新定义方法,则要在基类中声明为虚的,否则不用
每个对象有指向一个虚函数表的指针,保存虚函数地址
- 构造函数不能是虚函数,没意义
- 析构函数应该是虚函数除非类没有继承
- 友元不是虚函数,友元不是类成员,不能做虚函数,但可以使用虚成员函数
重新定义不会生成函数的两个版本,不是重载,会隐藏基类版本,不管参数特征标如何:重写
- 如果要重新定义应该确保与原型完全相同,如果返回类型为基类引用或指针,则可修改为派生类型的引用或指针(返回类型协变,covariance of return type)
- 如果基类声明被重载了,则应该在派生类中重新定义所有基类版本;如果只重新定义了一个版本则其他版本的基类函数将会被隐藏;如果重新定义时不需要修改,可以只调用基类版本就行
纯虚函数:xxx=0
不能创建包含纯虚函数的对象
如果基类使用了动态内存分配而派生类没有使用new则不需要给派生类显式定义析构函数,复制构造和复制运算符,否则必须定义
hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs)//baseDMA调用hasDMA的baseDMA部分进行构造
{
...
}
派生类的赋值运算符中使用基类的赋值运算符
baseDMA::operator=(hs) //copy base portion
派生类如何使用基类的友元?强制类型转换
std::ostream & operator<<(std::ostream & os, const hasDMA & hs)
{
os << (const baseDMA &) hs;//type cast
os << "New part";
return os;
}
构造函数不能被继承,析构函数不能被继承,赋值运算符不能被继承
派生类对象赋值给基类对象:只用到了基类的部分
基类对象赋值给派生类对象:错误,除非有转换构造函数:BrassPlus(const Brass &)
或者显式定义了基类赋值给派生类的赋值运算符
虚方法接受的参数是引用或指针类型
()