04 C++ 类的高级部分

静态成员

静态数据成员

  1. 用关键字 static 声明
  2. 同一个类中的所有对象都共享该变量
  3. 必须在类外定义和初始化,用 :: 来指明所属的类
  4. 静态数据成员实际上是在类外定义的一个变量,它的生存期和整个程序的生存期一样,在定义对象之前,静态数据成员就已经存在

静态函数成员

  1. 用关键字 static 声明
  2. 静态函数成员和静态数据成员类似,在对象生成之前已经存在
  3. 类外代码可以使用类名和作用域操作符来调用静态成员函数
  4. 静态成员函数只能引用属于该类的静态数据成员或静态成员函数
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)
    // 调用构造函数前 先调用冒号后的函数 按定义的先后调用
    {......}
};
posted @ 2025-04-22 18:33  YamadaRyou  阅读(36)  评论(0)    收藏  举报