day32-c++结构体,类

day32-c++结构体,类

一、c++引用和指针

指针和引用有以下几个主要的区别:

  • 指针是一个变量,存储的是一个地址,指向内存的一个存储单元;引用是原变量的一个别名,跟原来的变量实质上是同一个东西。

  • 指针可以有多级,引用只能是一级。

  • 指针可以在定义的时候不初始化,引用必须在定义的时候初始化。

  • 指针可以指向NULL,引用不可以为NULL。

  • 指针初始化之后可以再改变,引用不可以。

  • sizeof 的运算结果不同,指针返回指针类型的大小,引用返回原变量类型的大小。

  • 自增运算意义不同,指针自增之后指向后面的内存,引用自增相当于原变量自增。

所以,在一些场合下,比如动态内存分配、多态、链表、树等数据结构、函数参数等,指针是更合适或者必须的选择。当然,在一些场合下,比如函数返回值、常量对象等,引用是更安全或者更高效的选择。另外,在C++中还有一些智能指针(如unique_ptr, shared_ptr, weak_ptr等)和标准容器(如vector, list, map等),可以简化或者避免一些原生指针的使用。

下面是几个引用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
//传递函数参数:引用可以用来作为函数参数,传递变量的引用而不是副本,可以避免在函数调用时产生额外的内存开销,并且可以让函数修改原始数据。
void increment(int& num) {
num++;
}

int main() {
int a = 0;
increment(a);
cout << a << endl; // 输出1
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
//返回值:引用可以用来返回函数的结果,避免了拷贝构造函数的调用,提高程序的效率
int& max(int& a, int& b) {
return a > b ? a : b;
}

int main() {
int x = 10, y = 20;
max(x, y) = 30; // 将x赋值为30
cout << x << endl; // 输出30
return 0;
}




二、c++结构体和类

在c语言中,结构体只能包含数据成员,不能包含函数成员。而在c++中,结构体可以包含函数成员,甚至可以包含虚函数。这样,c++中的结构体就具有了类的功能,可以实现封装、继承和多态。但是,c++中的结构体和类还是有一些区别的,主要有以下几点:

  • 结构体的默认访问权限是public,而类的默认访问权限是private。这意味着,在结构体内部定义的成员变量和成员函数,默认都是公有的,可以在结构体外部访问。而在类内部定义的成员变量和成员函数,默认都是私有的,只能在类内部访问。
  • 结构体的默认继承方式是public,而类的默认继承方式是private。这意味着,在用一个结构体派生另一个结构体时,默认保持基类的访问权限不变。而在用一个类派生另一个类时,默认将基类的公有成员和保护成员变为私有成员。
  • 结构体不能作为模板参数,而类可以。这意味着,在定义模板时,不能用struct关键字来指定类型参数,只能用class或typename关键字。

例如,我们可以定义一个学生结构体,包含姓名、年龄、成绩等信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 定义一个学生结构体
struct Student {
// 数据成员
string name; // 姓名
int age; // 年龄
double score; // 成绩

// 构造函数
Student(string n, int a, double s) {
name = n;
age = a;
score = s;
}

//或者用初始化列表:<成员>(数值)
#if 0
Student(string n ,int a,double s) : name(n),age(a),score(s){

}
#endif
~Student(); //析构函数

// 成员函数
void show() {
cout << "姓名:" << name << endl;
cout << "年龄:" << age << endl;
cout << "成绩:" << score << endl;
}
};

Student ::~Student(){
cout<<"析构函数"<<denl;
}
// 主函数
int main() {
// 创建一个学生对象
Student s("张三", 18, 90.5);
// 调用成员函数
s.show();
return 0;
}

析构函数的作用是在对象销毁时释放对象分配的资源,如:

  • 释放动态分配的内存:如果在对象创建时使用了new操作符分配了动态内存,则需要在析构函数中使用delete操作符释放这些内存,以避免内存泄漏。
  • 关闭文件:如果在对象创建时打开了文件或其他资源,则需要在析构函数中关闭这些文件或资源,以避免资源泄漏。
  • 清理对象状态:如果对象有一些状态需要清理,例如释放锁、删除文件等,也可以在析构函数中完成。
    需要注意的是,析构函数是在对象销毁时自动调用的,无法手动调用,因此应该确保在析构函数中释放所有的资源和状态。



三、this指针

this指针是一个隐含的指针,它指向调用成员函数的对象。在成员函数内部,可以通过this指针来访问对象的数据成员和其他成员函数。this指针的类型是类类型的指针,例如,如果类名是Car,那么this指针的类型就是Car*。

this指针有以下几个作用:

  1. 区分同名的数据成员和形参。例如,如果成员函数的形参和数据成员同名,可以用this指针来区分它们。如下面的代码所示:
    1
    2
    3
    4
    5
    6
    7
    8
    class Car {
    public:
    int m_price; // 数据成员
    void SetPrice(int m_price) // 成员函数
    {
    this->m_price = m_price; // this指针指向调用该函数的对象
    }
    };
  2. 实现链式操作。例如,如果成员函数的返回值是类类型的引用,可以用this指针来返回当前对象的引用,从而实现链式操作。如下面的代码所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Car {
    public:
    int m_price; // 数据成员
    Car& SetPrice(int m_price) // 返回类类型的引用
    {
    this->m_price = m_price; // this指针指向调用该函数的对象
    return *this; // 返回当前对象的引用
    }
    };

    int main()
    {
    Car car;
    car.SetPrice(20000).SetPrice(30000); // 链式操作,先后给car对象赋值20000和30000
    return 0;
    }
  3. 作为函数的参数。例如,如果有一个友元函数或者全局函数,需要访问类的私有成员,可以用this指针作为参数传递给该函数。(和1差不多)如下面的代码所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Car {
    private:
    int m_price; // 私有数据成员
    public:
    void SetPrice(int m_price) // 成员函数
    {
    this->m_price = m_price; // this指针指向调用该函数的对象
    }
    friend void ShowPrice(Car* car); // 声明友元函数
    };

    void ShowPrice(Car* car) // 友元函数定义
    {
    cout << car->m_price << endl; // 可以访问私有数据成员
    }

    int main()
    {
    Car car;
    car.SetPrice(20000); // 给car对象赋值20000
    ShowPrice(&car); // 传递car对象的地址给友元函数
    return 0;
    }



四、分配和释放内存

C++中的new和delete操作符,以及它们和C语言中的malloc和free函数的区别。这些都是用于动态分配和释放内存的方法,但是它们有一些重要的不同点,需要我们了解和注意。

首先,new和delete是C++中的操作符,而malloc和free是C中的函数。这意味着new和delete可以被重载(就是自定义新的功能),而malloc和free不能。另外,new和delete可以根据对象的类型自动计算所需的内存大小,而malloc和free需要我们手动指定字节数。

其次,new不仅分配内存,而且会调用类的构造函数,delete会调用类的析构函数,而malloc只分配内存,不会进行初始化类成员工作,free不会调用析构函数。这意味着new和delete可以保证对象的完整性和正确性,而malloc和free可能会导致内存泄漏或未定义行为。

最后,new在分配内存失败时,会抛出异常,而malloc在分配内存失败时,会返回NULL。这意味着new需要使用try-catch语句来处理异常,而malloc需要使用if语句来检查返回值。另外,new可以使用定位new来指定分配内存的位置,而malloc不能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//C++中用于代替 malloc() 与 free();

int *p1 = new int; //开辟一个int
delete p1; //释放

char *ptr = new char[64]; //开辟64个char
delete [] ptr; //释放空间

int *ps = new int[10];
delete [] ps;

node *ptr_node = new node; //开辟一个node
delete ptr_node; //释放一个node

//注意:对于基本类型释放时直接delete ptr,对于数组空间 delete [] ptr



五、特殊的成员函数

5.1 构造函数

构造函数是一种特殊的成员函数,它的名字和类名相同,没有返回值,可以有参数也可以没有参数。构造函数的作用是在类的对象被创建时,对对象的数据成员进行初始化,为对象分配内存空间,并执行一些必要的操作。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Student {
public:
// 构造函数
Student(int id, string name) {
this->id = id;
this->name = name;
cout << "Student object created." << endl;
}
private:
int id;
string name;
};

int main() {
// 创建一个Student对象
Student s1(1001, "Alice");
// 输出:Student object created.
return 0;
}

5.2 析构函数

析构函数也是一种特殊的成员函数,它的名字是类名前加上一个波浪号(~),没有返回值,也没有参数。析构函数的作用是在类的对象被销毁时,对对象的数据成员进行清理,释放对象占用的内存空间,并执行一些必要的操作。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Student {
public:
// 构造函数
Student(int id, string name) {
this->id = id;
this->name = name;
cout << "Student object created." << endl;
}
// 析构函数
~Student() {
cout << "Student object destroyed." << endl;
}
private:
int id;
string name;
};

int main() {
// 创建一个Student对象
Student s1(1001, "Alice");
// 输出:Student object created.
// 程序结束时,s1对象被销毁
// 输出:Student object destroyed.
return 0;
}

5.3 赋值函数

首先,看一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class stu{
private:
int id;
string name;
public:
stu(int id,string name){
this->id = id;
this->name = name;
}
void setid(int id){
this->id = id;
}
void showid(){
cout<<this->id<<endl;
}
};

int main{}{
sut a(1,"tom");
sut b = a;
a.setid(2);
b.showid();//应该是1,但结果是2
}

上面的代码体现了对象如果直接赋值给另一个对象,并不是数据的拷贝,而只是把b指向了和a同一个空间。想要解决这种问题,c++引入了赋值函数和拷贝函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
public:
// 赋值运算符重载函数
A& operator=(const A& other) {
// 判断是否为自赋值
if (this != &other) {
// 释放当前对象占用的资源
// 根据other复制当前对象的成员变量
}
// 返回当前对象的引用
return *this;
}
};

赋值函数是一种特殊的成员函数,它的名字是operator=,有一个返回值类型为类类型的引用,有一个参数类型为类类型的常量引用。赋值函数的作用是在类的对象之间进行赋值操作时,对对象的数据成员进行逐个赋值,并返回左操作数的引用。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Person {
public:
Person(const char* name, int age) : m_age(age) {
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
}

// 赋值函数
Person& operator=(const Person& other) {
if (this != &other) { // 避免自我赋值
delete[] m_name;
m_age = other.m_age;
m_name = new char[strlen(other.m_name) + 1];
strcpy(m_name, other.m_name);
}
return *this;
}

~Person() {
delete[] m_name;
}

void print() {
std::cout << "name = " << m_name << ", age = " << m_age << std::endl;
}

private:
char* m_name;
int m_age;
};

int main() {
Person p1("Tom", 20);
Person p2("Jerry", 30);
p1 = p2; // 调用赋值函数
p1.print(); // 输出name = Jerry, age = 30
p2.print(); // 输出name = Jerry, age = 30
return 0;
}

5.4 拷贝函数

c++中拷贝函数是一种特殊的成员函数,它用于在创建对象时复制另一个对象的数据。拷贝函数的一般形式是:

1
2
3
类名(const 类名 &obj) {
// 拷贝obj的数据到当前对象
}

拷贝函数的作用是实现深拷贝,即不仅复制对象的基本类型成员,还复制对象的指针成员所指向的内存空间。如果没有定义拷贝函数,编译器会自动生成一个默认的拷贝函数,但它只能实现浅拷贝,即只复制对象的基本类型成员和指针值,而不复制指针所指向的内存空间。这样可能会导致内存泄漏或重复释放的问题。

拷贝函数的使用场景有以下几种:

  • 当一个对象作为参数传递给一个函数时,会调用拷贝函数创建一个临时对象。
  • 当一个对象作为返回值从一个函数返回时,会调用拷贝函数创建一个临时对象。
  • 当使用初始化列表或赋值运算符初始化一个对象时,会调用拷贝函数复制另一个对象的数据。

拷贝函数是c++中重要的特性之一,它可以保证对象的数据安全和完整。在编写c++程序时,应该根据需要合理地定义和使用拷贝函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Person {
public:
Person(const char* name, int age) : m_age(age) {
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
}

// 拷贝函数
Person(const Person& other) {
m_age = other.m_age;
m_name = new char[strlen(other.m_name) + 1];
strcpy(m_name, other.m_name);
}

~Person() {
delete[] m_name;
}

void print() {
std::cout << "name = " << m_name << ", age = " << m_age << std::endl;
}

private:
char* m_name;
int m_age;
};

int main() {
Person p1("Tom", 20);
Person p2 = p1; // 调用拷贝函数
p1.print(); // 输出name = Tom, age = 20
p2.print(); // 输出name = Tom, age = 20
return 0;
}

拷贝函数和赋值函数的区别主要有以下几点:

  • 拷贝函数用于创建新对象,赋值函数用于修改已存在的对象。
  • 拷贝函数只能有一个参数,即被拷贝的对象的引用,而赋值函数可以有多个参数,但第一个参数必须是被赋值的对象的引用。
  • 拷贝函数不需要返回值,而赋值函数需要返回被赋值的对象的引用,以便进行连续赋值。
  • 拷贝函数不需要考虑自拷贝的情况,而赋值函数需要在函数内部判断是否为自赋值,以避免出现错误。
  • 拷贝函数不需要释放当前对象占用的资源,而赋值函数需要在复制之前释放当前对象占用的资源,以防止内存泄漏。

如果使用默认的拷贝函数和赋值函数,则会进行浅拷贝。当类中含有指针类型的数据成员时要避免浅拷贝。如果需要进行深拷贝,则需要自定义拷贝函数和赋值函数,实现拷贝构造函数以及赋值操作时,为指针开辟新的空间。

5.5 友员函数

在C++中,友元(friend)是一种特殊的关系,它允许一个类的非成员函数或另一个类访问该类的私有成员和保护成员。这个关系可以在类的定义中通过friend关键字来声明。

友元函数是一个在类外定义的函数,它可以访问该类的私有成员和保护成员。声明一个函数为友元函数的方法是在该类的定义中使用friend关键字,后跟函数的声明。例如:

1
2
3
4
5
6
7
8
9
10
class MyClass {
private:
int x;
public:
friend void myFriendFunction(MyClass obj); // 友元函数声明
};

void myFriendFunction(MyClass obj) {
cout << obj.x; // 可以访问MyClass类的私有成员
}

同样,一个类也可以成为另一个类的友元。这个类就可以访问该类的私有成员和保护成员。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass {
private:
int x;
friend class MyFriendClass; // 友元类声明
};

class MyFriendClass {
public:
void myFunction(MyClass obj) {
cout << obj.x; // 可以访问MyClass类的私有成员
}
};

需要注意的是,友元关系是单向的,不具有传递性。即如果类A是类B的友元,类C是类A的友元,那么类C不能访问类B的私有成员和保护成员。友员函数虽然可以访问类的私有和保护成员,但它并不是该类的成员函数,也不受该类的访问控制规则的约束。因此,友员函数不能使用this指针,也不能直接访问该类的成员名,而必须通过对象名、引用或指针来访问。另外,友员函数也不能被继承或多态。友元虽然可以打破类的封装性,但应该谨慎使用,因为它破坏了类的封装性原则,使代码更加复杂和难以维护。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2024 nakano-mahiro
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信