二. 类和对象

文章参考:

《C++面向对象程序设计》✍千处细节、万字总结(建议收藏)_白鳯的博客-CSDN博客

相较于C语言的面向过程编程,C++改为面向对象编程,万物皆对象。

1. 构成

类一般由两部分构成:

  • 数据成员
  • 成员函数

访问权限可划分为三种,不同权限对应着不同的访问和继承规则:

  • public
  • protectd
  • private

一个简单的对象:

#include <iostream>
using namespace std;

class Student
{
private:					// 参数域
    long long id;
    int age;
    char* name;
    char sex;
public:	
    Student();      				// 空的构造函数    
    Student(long long id, int age, char* name, char sex);   // 构造函数
    ~Student();                 // 析构函数:用于删除对象

    void setId(long long id);
    long long getId();
};

Student::Student(){
    cout<<"这是空的构造函数"<<endl;
}

Student::Student(long long id, int age, char* name, char sex)
{
    this->id = id;
    this->age = age;
    this->name = name;
    this->sex = sex;
}

Student::~Student()
{
    delete this->name;  // 释放内存
}

void Student::setId(long long id){
    this->id = id;
}

long long Student::getId(){
    return this->id;
}


int main(){
    char* name = "zhangSan";
    Student* stu = new Student(123456789l, 18, name, '1');
    cout<< stu->getId() <<endl;
    return 0;
}

2. 成员函数

2.1 普通成员函数

定义:普通成员函数在类内部声明,然后在类外部完成函数的定义。注意在类外部定义类的成员函数时,需要以作用域来取ClassName::function()

EG:

class Student
{
private:
    long long id;
    int age;
    char* name;
    char sex;

public:         
    Student(long long id, int age, char* name, char sex);   // 构造函数
    ~Student();                 // 析构函数:用于删除对象

    void setId(long long id);
    long long getId();
};

Student::Student(){
    cout<<"这是空的构造函数"<<endl;
}

Student::Student(long long id, int age, char* name, char sex)
{
    this->id = id;
    this->age = age;
    this->name = name;
    this->sex = sex;
}

Student::~Student()
{
    delete this->name;  // 释放内存
}

void Student::setId(long long id){
    this->id = id;
}

long long Student::getId(){
    return this->id;
}

注意:

  • 必须是在类外定义,如果在类内部定义,那就是内联函数了。

2.2 内联成员函数

说明:所谓内联成员函数,和内联函数的效果一样,使用该函数的地方编译器会将该函数的代码复制过去,从而提高效率,用空间换时间,只不过这个内联函数是类的成员函数而已。

定义:分为两种:

  • 隐式声明:将成员函数直接定义在类内部,就像Java里一样,这样默认就是内联成员函数。

    class Score{
    private:
    	int mid_exam;
    	int fin_exam;
    public:
    	void setScore(int m, int f)		// 定义在内部,所以是内联函数
    	{
    		mid_exam = m;
    		fin_exam = f;
    	}
    	void showScore()
    	{
    		cout << "期中成绩: " << mid_exam << endl;
    		cout << "期末成绩:" << fin_exam << endl;
    	}
    };
    
  • 显式声明:在类外声明某成员函数时在前面加上inline关键字,从而显式声明其为内联成员函数。

    class Score{
    private:
    	int mid_exam;
    	int fin_exam;
    public:
    	void setScore(int m, int f);
    	void getScore();
    };
    inline void Score::setScore(int m, int f){
        mid_exam = m;
    	fin_exam = f;
    }
    inline void Score::getScore(){
        cout << "期中成绩: " << mid_exam << endl;
    	cout << "期末成绩:" << fin_exam << endl;
    }
    

注意:在使用inline定义内联成员函数时,必须将类的声明和定义放在同一个文件(或同一个头文件内),否则编译时将无法进行代码替换。

2.3 构造函数和析构函数

2.3.1 构造函数

说明:构造函数是一种特殊的成员函数,其存在的意义在于为对象分配空间,完成对象的初始化。

特点:

  • 没有返回值。
  • 命名与类一致。
  • 参数随意,可以不带。
  • 无需用户调用,建立对象时会自动调用。
  • 如果没有创建,编译器会自动生成一个无参构造函数。
  • 对于没有定义构造函数的类,可以使用成员初始化列表初始化公有成员

EG:

class Student
{
private:
    long long id;
    int age;
    char* name;
    char sex;

public:    
    Student();
    Student(long long id, int age, char* name, char sex);   // 构造函数
};

Student::Student(){
    cout<<"这是空的构造函数"<<endl;
}

Student::Student(long long id, int age, char* name, char sex)
{
    this->id = id;
    this->age = age;
    this->name = name;
    this->sex = sex;
}

成员初始化列表:

在对类进行初始化时,可以在构造函数内依次对成员赋值,也可以使用成员初始化列表对其进行赋值。

语法:

类名::构造函数名([参数表])[:(成员初始化列表)]{
    // 函数体
}

EG:

class Student
{
private:
    long long id;
    int age;
    char* name;
    char sex;

public:    
    Student();
    Student(long long id, int age, char* name, char sex);   // 构造函数
    Student(long long newId, int newAge, char* newName) : id(newId), age(newAge), name(newName), sex('1'){
        cout << "使用成员初始化列表进行初始化" << endl;
    }
};

Student::Student(){
    cout<<"这是空的构造函数"<<endl;
}

Student::Student(long long id, int age, char* name, char sex)
{
    this->id = id;
    this->age = age;
    this->name = name;
    this->sex = sex;
}

注意:类成员是按照它们在类里面被声明的顺序初始化的,与其在成员初始化列表里面的顺序无关。

2.3.2 析构函数

说明:析构函数是用来撤销对象的,主要负责释放对象内部成员的空间,完成一些连带业务等。

特点:

  • 名字与类名相同,但前面必须加一个~
  • 没有返回值和参数,无法重载。
  • 无需用户调用,当撤销对象时,编译系统会自动调用析构函数。
  • 如果没有定义,编译器将会创建一个默认的析构函数。

EG:

#include <iostream>
using namespace std;

class Student
{
private:					// 参数域
    long long id;
    int age;
    char* name;
    char sex;
public:	
    ~Student();                 // 析构函数:用于删除对象
};

Student::~Student()
{
    delete this->name;  // 释放内存
}

int main(){
    char* name = "zhangSan";
    Student* stu = new Student(123456789l, 18, name, '1');
    cout<< stu->getId() <<endl;
    return 0;
}

调用逻辑:

  • 如果是一个全局对象,当离开其作用域时,自动调用。
  • 如果定义在函数内,函数结束时,自动调用。
  • 如果使用new创建出来的,使用delete手动释放。

2.3.3 拷贝构造函数

说明:是一种特殊的构造函数,但其形参为本类对象的引用,其本质上是根据一个已经存在的对象,来拷贝出一个新的对象。

EG:

#include <iostream>
using namespace std;

class Score
{
private:
    int mid_exam;
    int fin_exam;
public:
    Score(const Score &score);  // 拷贝构造函数
    
    int getMidExam();
};

Score::Score(const Score &score){
    this->mid_exam = score.mid_exam;
    this->fin_exam = score.fin_exam;
}

int Score::getMidExam(){
    return this->mid_exam;
}

int main(int argc, char* argv[]){
    // 在C++中,NULL被强定义为0,在重载时有时会产生二义性。因此推荐使用nullptr,表示空指针。
    Score *p = nullptr;    
    Score s1(80, 90);
    Score s2(s1);
    cout << s1.getMidExam() << endl;    // 80
    cout << s2.getMidExam() << endl;    // 80

    return 0;
}

注意:

  • 在拷贝构造函数中,可以直接访问传入对象的所有属性,即使是private级别也可以直接访问。

浅拷贝与深拷贝:

拷贝构造函数分为两种:浅拷贝和深拷贝。

  • 浅拷贝:这也是默认的拷贝构造函数。直接将传入对象的值依次赋予要创建对象的数据成员。但此时如果数据成员中有指针,就会导致两个对象的内部的指针成员指向同意块空间,如果有一个对象被撤销了,那这片空间也就释放了,此时如果撤销另一个对象,就会导致那片空间再次遭到释放,这会带来错误。

    class Student{
    public:
        Student(char *name1, float score1);
        ~Student();
    private:
        char *name;
        float score;
    };
    
    Student stu1("白", 89);
    // Student stu2 = stu1; 这句话会报错
    
  • 深拷贝:将传入对象的值依次赋予要创建对象的数据成员。而对于指针类型变量,为其生成生存空间,随后赋值。

    Student::Student(const Student& stu)
    {
        name = new char[strlen(stu.name) + 1];
        if (name != 0) {
            strcpy(name, stu.name);
            score = stu.score;
        }
    }
    

3. 自引用指针this

this指针保存了当前对象的地址,称为自引用指针。

Score(Score &s);
Score::Score(Score &s){
    if (this == &s)		// 比较地址
        return ;
    this = &s;
}

4. string类

说明:

对于字符串,C语言在string.h头文件中封装了许多方法,用于操作以\0结尾的字符串数组。

而在C++中,除了支持C语言提供的方法外,还提供了个字符串类:string,其封装了对于字符串处理所需的常用操作。

使用前必须引入文件:#include <string>

字符串常用运算符:

=  +=  >  <  >=  <= == !=  [](访问对应下标的字符)  >>(输入)  <<(输出) 

EG:

void stringFunc(){
    string str1 = "abc";
    string str2("123");
    cout<< str1 + str2 <<endl;      // abc123     
    cout<< (str1 == str2) <<endl;   // 0
}

5. 静态成员

5.1 静态数据成员

定义:

在一个类中,如果某个成员被声明为static,则该成员是静态数据成员,不论这个类建立多少个对象,所有对象都共享这一份静态数据成员,从而实现了不同对象之间的数据共享。

特点:

  • 静态数据成员的初始化应在类外单独进行,且应在定义对象之前进行。(因为它要被所有该类的对象共享)
  • 静态数据成员属于类而不是属于单独某一个对象,因此可以通过类名访问(实际上是作用域访问,前提是其被声明为static),即:类名::静态数据成员名
  • 静态数据成员和静态变量一样,在编译时创建并初始化。可以被由该类所创建的所有对象访问,访问方法为:
    • 对象名.静态数据成员名
    • 对象指针->静态成员数据名

5.2 静态成员函数

定义:

在一个类中,如果某个成员函数前有static,那么该成员函数是静态成员函数,不论这个类建立多少个对象,所有对象都共享这一份静态成员函数。且静态成员函数的作用是处理静态数据成员,而不是为了进行数据通信。格式如下:

static 返回类型 函数名 (参数列表);

调用:

三种方式:

类名::静态成员函数名(实参表);
对象名.静态成员函数名(实参表);
对象指针->静态成员函数名(实参表);

特点:

  • 一般情况下,静态成员函数不访问非静态成员。如果确实需要,只能通过对象名/对象指针/对象引用进行访问。
  • 私有静态成员不能被类外部的函数和对象访问。
  • 编译系统将静态成员函数定义为内连接,因此假如与当前文件相连接的其它文件中有同名函数,也不会与该静态成员函数发生冲突。

6. 友元

C++提供了私有成员和保护成员,而这些成员只能通过类的成员函数进行访问。但有时我们需要对私有/保护成员频繁进行访问操作,这时多次调用成员函数会带来较大的时间和空间开销,导致程序的效率降低。因此C++提供了友元来对私有/保护成员进行访问。友元包括:

  • 友元函数
  • 友元类

6.1 友元函数

简介:

友元函数有两种:

  • 非成员函数
  • 成员函数

通过友元函数,我们直接对指定类的私有、保护、公有成员进行访问。

6.1.1 非成员函数声明为友元函数

  • 声明:在要访问的目标类内声明,最前面要加上friend关键字。

  • 定义:在类的外部定义。虽然友元函数在目标类内声明,但它并不是成员函数,因此在类外进行定义时并不需要加上类名::

  • EG:

    #include <iostream>
    using namespace std;
    class People{
    private:
    	int id;
    	int age;
    public:
    	People(int id, int age);
        ~People();
        // 友元函数
    	friend int getId(People &p);
    };
    
    People::People(int id, int age)
    {
    	this.id = id;
    	this.age = age;
    }
    // 并不是成员函数,因此定义时无需加上"类名::"进行限定。
    int getId(People &p){
        return p.id;
    }
    
  • 特点:

    • 虽然友元函数在目标类内声明,但它并不是成员函数,因此在类外进行定义时并不需要加上类名::

    • 因为友元函数不是类的成员函数,因此无法直接访问类的数据成员,也无法通过this指针访问,必须通过作为入口参数传递过来的对象名(或对象引用、对象指针)来访问类的数据成员。

    • 一个函数可以同时称为多个类的友元函数。例如:

      #include <iostream>
      using namespace std;
      // 预定义,否则下面友元函数会报错
      class Student;
      
      class People{
      private:
      	int id;
      	int age;
      public:
      	People(int id, int age);
          ~People();
          // 友元函数
      	friend void show_id(People &p, Student &s);
      };
      
      class Student{
      private:
      	int id;
      	int age;
      public:
      	Student(int id, int age);
          ~Student();
          // 友元函数
      	friend void show_id(People &p, Student &s);
      };
      
      
      // 并不是成员函数,因此定义时无需加上"类名::"进行限定。
      void show_id(People &p, Student &s){
          cout << "people's id = " << p.id << endl;
          cout << "student's id = " << s.id << endl;
      }
      

6.1.2 成员函数声明为友元函数

  • 作用:将成员函数声明为友元类,该函数不仅可以访问自己所在类对象中的私有成员,也可以通过friend访问目标类的私有成员。

  • EG:

    #include <iostream>
    using namespace std;
    // 预定义,否则下面友元函数会报错
    class Student;
    
    class People{
    private:
    	int id;
    	int age;
    public:
    	People(int id, int age);
        ~People();
        // 友元函数
    	void show_id(Student &s);
    };
    
    class Student{
    private:
    	int id;
    	int age;
    public:
    	Student(int id, int age);
        ~Student();
        // 友元函数,也是People的成员函数
    	friend void show_id(Student &s);
    };
    
    
    // 是成员函数,因此定义时需要加上"类名::"进行限定。
    void People::show_id(Student &s){
        cout << "people's id = " << this..id << endl;
        cout << "student's id = " << s.id << endl;
    }
    
  • 特点:

    • 因为是成员函数,所以在类外定义时需要加上类名::
    • 因为是成员函数,所以可以通过this指针访问当前对象的成员。

6.2 友元类

可以见一个类声明为另一个类的友元。

class Y{
    ···
};
class X{
    friend Y;    //声明类Y为类X的友元类
};

通过将Y声明为X的友元类,将Y中所有的函数都变为X的友元函数,也就是说,Y中所有的函数都可以访问X都的所有成员。

注意:

友元关系不具有交换性传递性

7 类的嵌套

定义:将一个类作为另一个类的数据成员,就是类的嵌套。

EG:

class Y{
    ···
};
class X{
    Y y;
    ···
};

8 共享数据的保护

8.1 常引用

定义:const 类型& 引用名

int a = 10;
// 常引用,无法对引用进行再次赋值
const int&b = a;
void func(int& m, int& n){}		// 此函数内无法修改m、n的值

8.2 常对象

定义:类名 const 对象名[(参数表)];

const Person p(1, 18);

特点:

  • 常对象在声明时必须赋初值,不能先声明再定义。
  • 常对象只能调用它的常成员函数,不能调用普通成员函数。

8.3 常成员对象

8.3.1 常数据成员

使用const修饰的数据对象,一旦赋值,无法修改。且只能通过成员初始化列表对该数据成员进行赋值

8.3.2 常成员函数

声明:类型 函数名 (参数列表) const;

特点:

  • 在声明和定义时都要加上const,调用时不用。
  • 使用const可以对重载参数进行区分。
  • 既可以访问静态数据成员,也可以访问一般数据成员。
  • 但是不能更新数据成员,也不能调用该类的普通成员函数。这是为了保证常成员函数不对类的数据成员进行修改操作。

EG:

class Date{
private:
	int year;
	int month;
	int day;
public:
	Date(int y, int m, int d) : year(y), month(m), day(d){}
	void showDate();
	void showDate() const;
};

void Date::showDate() {
	//···
}

void Date::showDate() const {
	//···
}
posted @   BinaryPrinter  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示