一、构造函数
1.1 定义与作用
构造函数是一种特殊的成员函数,在创建对象时自动被调用,用于对对象进行初始化。其函数名与类名相同,没有返回值类型(包括 void
也不能有 )。
1.2 特性
- 自动调用:每当创建类的对象时,构造函数都会被自动调用。
- 默认构造函数:如果用户没有定义任何构造函数,编译器会自动生成一个默认构造函数。这个默认构造函数没有参数,函数体为空。但如果用户定义了至少一个构造函数,编译器就不会再自动生成默认构造函数。
1.3 调用形式
- 无参构造函数调用:
Stu za;
,这种形式调用的是无参构造函数。 - 有参构造函数调用:
Stu za = Stu(参数);
,等价于Stu za(参数);
,这两种形式都是调用带参数的构造函数。Stu za = 参数;
,当参数类型匹配时,也会调用相应的带参构造函数。
1.4 初始化列表
当类中有引用成员时,必须在构造函数的初始化列表中对其进行初始化,否则会导致编译错误。因为引用必须在定义时就被初始化,且之后不能再重新绑定到其他对象。
1.5 示例代码
#include <iostream>
class Stu {
private:int age;
public:// 无参构造函数Stu() {age = 0;std::cout << "无参构造函数被调用" << std::endl;}// 有参构造函数Stu(int a) {age = a;std::cout << "有参构造函数被调用,年龄为: " << age << std::endl;}
};
int main() {Stu stu1; // 调用无参构造函数Stu stu2(20); // 调用有参构造函数Stu stu3 = 22; // 调用有参构造函数return 0;
}
二、引用
2.1 在构造函数中的应用
在构造函数的参数列表中使用引用类型,可以避免在函数调用时对传递的对象进行不必要的拷贝,从而提高程序的效率。特别是当传递的对象较大或者拷贝操作比较复杂时,这种方式的优势更加明显。
2.2 示例代码
#include <iostream>
class BigObject {// 假设这里有很多成员变量,使得对象占用较大空间int data[1000];
public:BigObject() {// 简单初始化for (int i = 0; i < 1000; ++i) {data[i] = i;}}
};
class Container {
private:BigObject obj;
public:// 使用引用传递参数,避免拷贝Container(const BigObject& bigObj) : obj(bigObj) {std::cout << "通过引用传递对象,避免了不必要的拷贝" << std::endl;}
};
int main() {BigObject big;Container container(big);return 0;
}
三、接口函数(set 和 get 接口 )
3.1 定义与作用
- set 接口:专门用于给类的私有成员变量赋值,通常命名形式为
set + 成员变量名
。 - get 接口:用于获取类的私有成员变量的值,通常命名形式为
get + 成员变量名
。
3.2 示例代码
#include <iostream>
#include <string>
class Person {
private:std::string name;int age;
public:// set 接口void setName(const std::string& n) {name = n;}void setAge(int a) {age = a;}// get 接口const std::string& getName() const {return name;}int getAge() const {return age;}
};
int main() {Person person;person.setName("Alice");person.setAge(25);std::cout << "姓名: " << person.getName() << ", 年龄: " << person.getAge() << std::endl;return 0;
}
四、this 指针
4.1 定义与作用
当对象调用成员函数时,this
指针会作为隐含参数传递给该函数,它指向调用该函数的对象本身。通过 this
指针,可以在成员函数中访问对象的成员变量和其他成员函数,并且可以区分成员变量和函数参数(当参数名与成员变量名相同时 )。
4.2 示例代码
#include <iostream>
class Point {
private:int x;int y;
public:Point(int x, int y) {// 使用 this 指针区分参数和成员变量this->x = x;this->y = y;}void print() {std::cout << "坐标: (" << x << ", " << y << ")" << std::endl;}
};
int main() {Point p(3, 4);p.print();return 0;
}
五、析构函数
5.1 定义与作用
析构函数也是一种特殊的成员函数,与构造函数相反,它在对象生命周期结束时自动被调用,用于释放对象在生命周期内分配的资源(如动态分配的内存 ),执行一些清理工作。析构函数名是在类名前加上 ~
,没有参数,也没有返回值类型。
5.2 特性
- 自动调用:当对象超出作用域、使用
delete
释放对象(针对动态分配的对象 )等情况时,析构函数会自动被调用。 - 默认析构函数:如果用户没有定义析构函数,编译器会自动生成一个默认析构函数。对于普通类,默认析构函数函数体为空;但对于包含动态分配资源或其他需要特殊清理操作的类,通常需要用户自定义析构函数。
5.3 示例代码
#include <iostream>
class Resource {
private:int* data;
public:Resource() {data = new int[10];std::cout << "构造函数被调用,分配内存" << std::endl;}~Resource() {delete[] data;std::cout << "析构函数被调用,释放内存" << std::endl;}
};
int main() {{Resource res;} // res 对象超出作用域,析构函数被自动调用return 0;
}
六、拷贝构造函数
6.1 定义与作用
拷贝构造函数是一种特殊的构造函数,用于用一个已存在的对象来初始化同类型的新对象。其函数参数是本类对象的引用(通常为 const 类名&
形式 )。
6.2 调用场景
- 函数参数传递:当函数的参数是对象值传递时,会调用拷贝构造函数创建一个实参对象的副本传递给函数。
- 函数返回值:当函数返回一个对象时,如果返回方式是值返回,会调用拷贝构造函数创建一个临时对象作为返回值。
6.3 传参形式(const 类名 & )原因
- 避免拷贝:使用引用传递参数,避免了在函数调用时对传递的对象进行额外的拷贝,提高了效率。
- 防止修改:
const
修饰可以防止在拷贝构造函数内部意外修改传入的对象。
6.4 浅拷贝与深拷贝
- 浅拷贝:默认的拷贝构造函数(即编译器自动生成的 )是浅拷贝。它只是简单地复制对象的成员变量的值,对于指针类型的成员变量,只是复制指针的值(即指向的地址 ),这样会导致新对象和原对象共享同一块动态分配的内存。当对象析构时,可能会出现多次释放同一块内存等问题。
- 深拷贝:当类中包含指针类型的成员变量,且该指针指向动态分配的资源时,通常需要自定义拷贝构造函数来实现深拷贝。深拷贝会为新对象重新分配一块与原对象相同大小的内存,并将原对象中指针指向的内容复制到新分配的内存中,使得新对象和原对象拥有各自独立的资源副本。
6.5 示例代码
#include <iostream>
#include <cstring>
class String {
private:char* str;
public:String(const char* s) {str = new char[strlen(s) + 1];strcpy(str, s);std::cout << "构造函数被调用" << std::endl;}// 浅拷贝构造函数(错误示范)String(const String& other) {str = other.str;std::cout << "浅拷贝构造函数被调用(错误方式)" << std::endl;}// 深拷贝构造函数String(const String& other) {str = new char[strlen(other.str) + 1];strcpy(str, other.str);std::cout << "深拷贝构造函数被调用" << std::endl;}~String() {delete[] str;std::cout << "析构函数被调用" << std::endl;}
};
int main() {String s1("hello");String s2(s1);return 0;
}
七、static
7.1 静态存储区变量
- 生命周期:静态存储区的变量在程序启动时分配内存,在程序结束时释放内存,其生命周期贯穿整个程序的运行过程。
- 初始化:如果没有显式地对静态存储区变量进行初始化,那么对于数值类型的变量,会自动初始化为 0;对于指针类型的变量,会自动初始化为
NULL
。 - 作用域:静态存储区变量在其定义的作用域内有效。如果是在函数内部定义的局部静态变量,它的作用域局限于该函数内部,但它的生命周期并不受函数调用结束的影响,在下次函数调用时,它的值会保持上一次函数调用结束时的值。
7.2 类的静态成员变量
- 共享性:类的静态成员变量被该类的所有对象共享,无论创建了多少个该类的对象,静态成员变量在内存中只有一份拷贝。
- 存储位置:静态成员变量存储在静态存储区,并不属于任何一个具体的对象实例。
- 初始化:静态成员变量一般需要在类外进行初始化,格式为
数据类型 类名::静态成员变量名 = 初始值;
。 - 访问方式:可以通过类名直接访问(
类名::静态成员变量名
),也可以通过对象访问(对象名.静态成员变量名
)。
7.3 类的静态成员函数
- 无 this 指针:静态成员函数没有
this
指针,因为它并不属于任何一个具体的对象,所以不能直接访问类的非静态成员变量和非静态成员函数。 - 访问权限:静态成员函数遵循类的访问控制规则,即如果是公有的静态成员函数,可以在类外通过类名或对象进行访问;如果是私有的静态成员函数,则只能在类内部访问。
- 用途:常用于提供与类相关的工具函数,或者用于访问和操作类的静态成员变量。
7.4 示例代码
#include <iostream>
class Counter {
private:static int count;
public:Counter() {++count;}~Counter() {--count;}static int getCount() {return count;}
};
int Counter::count = 0;
int main() {Counter c1, c2;std::cout << "对象数量: " << Counter::getCount() << std::endl;return 0;
}
八、单例模式
8.1 饿汉模式
- 原理:饿汉模式是在程序启动时就立即创建单例对象,由于是在单线程初始化阶段创建,所以不存在线程安全问题。
- 实现方式:定义一个静态的单例对象,并在类外进行初始化。提供一个静态成员函数用于获取该单例对象。
8.2 懒汉模式
- 原理:懒汉模式是在第一次使用单例对象时才进行创建,这种方式相对饿汉模式更加 “懒惰”,可以在一定程度上提高程序的启动效率。
- 非线程安全实现:在获取单例对象的静态成员函数中,首先判断单例对象是否已经创建,如果没有创建则进行创建,然后返回单例对象。
- 线程安全实现:为了在多线程环境下保证懒汉模式的正确性,需要使用锁机制(如
std::mutex
)来防止多个线程同时创建单例对象。在获取单例对象的函数中,加锁后再进行对象是否已创建的判断和创建操作。
8.3 示例代码
#include <iostream>
#include <mutex>
// 饿汉模式
class SingletonEager {
private:static SingletonEager instance;SingletonEager() {}
public:static SingletonEager& getInstance() {return instance;}
};
SingletonEager SingletonEager::instance;
// 懒汉模式(非线程安全)
class SingletonLazy {
private:static SingletonLazy* instance;SingletonLazy() {}
public:static SingletonLazy* getInstance() {if (instance == nullptr) {instance = new SingletonLazy;}return instance;}
};
SingletonLazy* SingletonLazy::instance = nullptr;
// 懒汉模式(线程安全)
class SingletonThreadSafe {
private:static SingletonThreadSafe* instance;static std::mutex mutex_;SingletonThreadSafe() {}
public:static SingletonThreadSafe* getInstance() {std::lock_guard<std::mutex> guard(mutex_);if (instance == nullptr) {instance = new SingletonThreadSafe;}return instance;}
};
SingletonThreadSafe* SingletonThreadSafe::instance = nullptr;
std::mutex SingletonThreadSafe::mutex_;
int main() {SingletonEager& eager = SingletonEager::getInstance();SingletonLazy* lazy = SingletonLazy::getInstance();SingletonThreadSafe* threadSafe = SingletonThreadSafe::getInstance();return 0;
}
九、new 与 delete
9.1 new 的用法
new
是 C++ 中用于动态内存分配的关键字,它在分配内存的同时会调用对象的构造函数进行初始化。
- 分配单个对象:
Stu* za = new Stu;
:调用无参构造函数创建一个Stu
类的对象,并返回指向该对象的指针。Stu* za = new Stu(参数);
:调用带参构造函数创建一个Stu
类的对象,并返回指向该对象的指针。
- 分配对象数组:
Stu* arr = new Stu[10];
:创建一个包含 10 个Stu
类对象的数组,调用无参构造函数初始化每个元素。Stu* arr = new Stu[10](参数);
:如果Stu
类有合适的构造函数,会调用该构造函数初始化数组中的每个元素。
- 分配基本数据类型变量和数组:
int* a = new int;
:创建一个未初始化的int
类型变量。int* a = new int(5);
:创建一个初始化为 5 的int
类型变量。int* arr = new int[10];
:创建一个包含 10 个int
类型元素的数组,元素未初始化。int* arr = new int[10]();
:创建一个包含 10 个int
类型元素的数组,元素初始化为 0。
9.2 delete 的用法
delete
是与 new
对应的用于释放动态分配内存的关键字,它会调用对象的析构函数(如果是对象 )。
- 释放单个对象:
Stu* za = new Stu; delete za;
,释放za
指向的Stu
类对象。 - 释放对象数组:
Stu* arr = new Stu[10]; delete[] arr;
,释放arr
指向的Stu
类对象数组,会依次调用数组中每个对象的析