04 C++ 类的高级部分
静态成员
静态数据成员
- 用关键字
static声明 - 同一个类中的所有对象都共享该变量
- 必须在类外定义和初始化,用
::来指明所属的类 - 静态数据成员实际上是在类外定义的一个变量,它的生存期和整个程序的生存期一样,在定义对象之前,静态数据成员就已经存在
静态函数成员
- 用关键字
static声明 - 静态函数成员和静态数据成员类似,在对象生成之前已经存在
- 类外代码可以使用类名和作用域操作符来调用静态成员函数
- 静态成员函数只能引用属于该类的静态数据成员或静态成员函数
class Person
{
static unsigned count;
public:
static void inc_count()
{count++;}
static void pri_count()
{cout<<"count:"<<count<<endl;}
}
unsigned Person::count=0; //也可不初始化,全局变量默认为 0
int main()
{
Person::pri_count();
}
友元
在函数前面加friend,声明为一个类的友元
友元函数不是类中的函数成员,但和类的函数成员一样,可以访问类中定义的私有成员
友元函数可以是外部函数,也可以是另一个类的函数成员
外部函数作为类的友元
class Point
{
private:
int xPos,yPos;
public:
Point(int xx=0,int yy=0)
{xPos=xx;yPos=yy;}
int GetXPos(){return xPos;}
int GetYPos(){return yPos;}
friend double Distance(Point &a,Point &b); // 可以放在类中任何地方
// 可以访问私有成员 xPos yPos
};
double Distance(Point &a,Point &b)
{
double dx=a.xPos-b.xPos,dy=a.yPos-b.yPos;
return sqrt(dx*dx+dy*dy);
}
void main()
{
Point p1(3,5),p2(4,6);
cout<<Distance(p1,p2)<<endl;
}
类的成员函数作为另一个类的友元
友元函数可以访问自己所在类对象和friend声明语句所在类对象中的私有成员和公有成员
class Budget
{
static float corp;
float div;
public:
friend void Aux::add(float,Budget &); // 加上类名
};
void Aux::add(float b,Budget &div)
{
aux+=b;
div.corp+=aux;
}
类作为另一个类的友元
若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员
class A
{
int x;
friend class B;
public:
void Display(){cout<<x<<endl;}
};
class B
{
A a;
public:
void Set(int i){a.x=i;}
void Display(){a.Display();}
};
对象赋值问题
采用赋值运算符 = 可以将一个对象赋值给另外一个对象,或者采用一个对象初始化另外一个对象。在缺省情况下,这两个操作执行的是对象成员之间的拷贝,也称为按位拷贝或浅拷贝
初始状态
class Person
{
public:
char *name;
unsigned age;
Person(const char* n = "notfound", unsigned a = 404)
{
name = new char[strlen(n) + 1];
strcpy(name, n);
age = a;
cout << "copy constructor1" <<endl;
}
void print()
{
cout<<name<<" "<<age<<endl;
}
};
初始化和赋值时会出现问题,name 均指向同一内存区域
Person p3=p2; // 初始化
p1=p2; // 赋值
strcpy(p1.name,"Lily");
// 结果: p1 p2 p3 的 name 都会变成 Lily
实现深拷贝
// 浅拷贝:多个对象共用同一块内存(可能会多次释放同一内存导致崩溃)
p1.name=p2.name;
// 深拷贝:
strcpy(p1.name,p2.name);
添加拷贝构造函数
实现对象赋值 p1 = p2
Person(const Person& p) // 引用 避免调用拷贝构造函数
{
name = new char[strlen(p.name) + 1];
//name = p.name; wrong! // 浅拷贝
strcpy(name, p.name); // 深拷贝
age = p.age; // 不涉及指针指向 可以直接赋值
cout << "copy constructor2" <<endl;
}
Person p3(p2);
p1=p2;
// 结果: p3 正确 , p1 p2 相同
重载赋值运算符
添加拷贝构造函数后,能解决初始化问题,但无法解决对象赋值问题
不需要 name = new char[strlen(p.name)+1]; ,this.name 在定义时已经分配了空间
void operator=(const Person &p) // 运算符重载
{
strcpy(name, p.name);
age = p.age;
cout << "operator=\n";
}
p.name 可能大于当前 name 的内存
if (strlen(name) < n)
{
delete[] name;
name = new char[n+1];
}
Person p3 = p2; // 等价于 Person p3(p2); 调用拷贝构造函数
p1 = p2; // 等价于 p1.operator=(p2); 重载赋值运算符
// 结果: p1 p2 p3 分配了不同内存空间 正确
添加析构函数
用 new 分配了新内存,需要删除
如果使用浅拷贝,析构函数会重复释放同一块内存造成崩溃
~Person()
{
delete[] name;
}
检查自赋值
if (this == &p) return;
修改返回值
p3 = p2 = p1 等价于 p3 = (p2 operator= p1) ,但是 operator= 返回值是 void ,需要修改返回值
Person operator=(const Person &p) // 返回临时对象
Person& operator=(const Person &p) // 返回引用 不调用拷贝构造函数 效率更高
最终写法
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
public:
char *name;
unsigned age;
Person(const char* n = "notfound", unsigned a = 404)
{
name = new char[strlen(n) + 1];
strcpy(name, n);
age = a;
cout << "copy constructor1" <<endl;
}
Person(const Person& p) // 引用 避免递归调用拷贝构造函数
{
name = new char[strlen(p.name) + 1];
strcpy(name, p.name); // 深拷贝
age = p.age; // 不涉及指针指向 可以直接赋值
cout << "copy constructor2" <<endl;
}
Person& operator=(const Person &p) // 运算符重载
{
if (this == &p) return *this; // 避免 p1 = p1 的情况
size_t n = strlen(p.name);
if (strlen(name) < n)
{
delete[] name;
name = new char[n+1];
}
strcpy(name, p.name);
age = p.age;
cout << "operator=\n";
return *this;
}
~Person()
{
delete[] name; // 如果使用浅拷贝,析构函数会重复释放同一块内存造成崩溃
}
void print()
{
cout<<name<<" "<<age<<endl;
}
};
int main()
{
...
return 0;
}
函数调用
函数传参
void f(Person p)//Person p=p1 or Person p(p1) // 调用
{...}
void f(Person &p)//Person &p=p1 // 不调用
{...}
void f(Person *p)//Person *p=&p1 // 不调用
{...}
函数返回值
Person f(Person &p)
{return p;} // Person anonymous = p; // 调用
Person *f(Person &p)
{return &p;} // Person *anonymous = &p; // 不调用
Person &f(Person &p)
{return p;} // Person &anonymous = p; // 不调用
运算符重载
重载双目运算符
Obj1 B Obj2; // 等价于
Obj1.operator B(Obj2);
c = a + b; // 等价于
c = a.operator + (b);
// 重载加号 +
Count operator+(const Count &b)
{
Count t;
t.x=x+b.x;
t.y=y+b.y;
return t;
}
重载单目运算符
// 重载前置自增运算符 ++()
Person operator++()
{
age++;
return *this;
}
// 重载后置自增运算符 ()++
Person operator++(int) // int 只用于区分前置和后置
{
Person p(name,age);
age++;
return p;
}
重载关系运算符
// 重载大于号 >
bool operator>(const Person &p) // 在类内定义
{
if (this->age > p.age) return true;
else return false;
}
bool operator>(const Person &l,const Person &r) // 在类外定义
{
if(l.age>r.age) return true;
else return false;
}
// 重载等于号 ==
bool operator==(const Person& p)
{
if (age == p.age) return true;
else return false;
}
p1 > p2; // 等价于
p1.operator>(p2); //类内定义
operator>(p1,p2); // 类外定义
重载流操作符
Person p1;
cin>>p1;cout<<p1; // 等价于
operator>>(cin,p1);
operator<<(cout,p1);
class Data
{
private:
int a,b;
public:
......
friend ostream &operator<<(ostream &,Data &);
friend istream &operator>>(istream &,Data &);
};
// os 不可加上 const 内容会发生改变
ostream &operator<<(ostream &os,const Data &obj)
{
os<<obj.a<<"a, "<<obj.b<<" b";
return os; // 返回 cout 可实现 (cout<<x)<<y;
}
// 输入中 类不可加上 const
istream &operator>>(istream &is,Data &obj)
{
cout<<"a: ";is>>obj.a;
cout<<"b: ";is>>obj.b;
return is;
}
重载类型转换运算符
class FeetInches
{
int feet,inches;
public:
...
operator float(); // 不需要也不允许写返回类型
operator int(){return feet;}
};
FeetInches::operator float()
{
float temp=feet;
temp+=(inches/12.0f);
return temp;
}
void main()
{
FeetInches dis;
float f;
int i;
cin>>dis;
f=dis;
i=dis;
}
重载[]操作符
class IntArray
{
int *aptr;
int arraySize;
void memError();
void subError();
public:
IntArray(int);
IntArray(const IntArray &);
~IntArray();
int size(){return arraySize;}
int &operator[](const int &); // 必须是引用才能修改值 a[0]=1;
};
...
int & IntArray::operator[](const int &sub)
{
if ( sub<0 || sub>=arraySize)
subError();
return aptr[sub];
}
对象组合
让某个类的对象作为另一个类中的数据成员出现
构造函数调用顺序:先调用内嵌对象的构造函数(按内嵌时的声明顺序,先声明者先构造),然后调用本类的构造函数(析构的顺序相反)
若调用缺省构造函数(即无形参的),则内嵌对象的初始化也将调用相应的缺省构造函数
class Mystring
{
public:
Mystring(int n)
{...}
}
class Customer
{
public:
MyString name;
MyString address;
MyString city;
MyString state;
Account savings; //储蓄账户
Account check; //支票账户
Customer(char *n,char *a,char *c,char *s,int k) // 将传参依次分给各函数
: name(n),address(a),city(c),state(s)
// 调用构造函数前 先调用冒号后的函数 按定义的先后调用
{......}
};

浙公网安备 33010602011771号