构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的成员变量。
它的名称与类的名称完全相同,没有返回值类型,也没有返回值,可以带有参数,也可以没有参数。
// 构造函数可以重载,需要满足函数的重载条件
在创建对象时,构造函数会自动被调用,初始化对象的成员变量。
一个类可以定义多个构造函数,但是它们的形参列表必须不同(重载)。
自己写了构造函数后,不管自己写的构造函数是不是带参数的,系统都不会再自动生成构造函数。
如果函数指向实现在类体里边,默认就是内联函数,平时写函数一般需要将函数声明在类体中,在类的外边去实现。
class Person {
public:
Person(); // 无参构造函数
Person(const std::string& name, int age); // 有参构造函数
~Person(); // 析构函数
private:
std::string m_name;
int m_age;
};
2. 如何使用构造函数?
使用构造函数时,需要在类定义的内部声明构造函数,同时还需要实现它。
构造函数可以用来初始化对象的成员变量,对它们进行一些初始化操作和初始化检查等。
在创建对象时,需要调用相应的构造函数,该构造函数会执行对象的初始化。
一般情况下,创建对象的方式有两种:栈上创建和堆上创建。
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
Person() {} // 无参构造
Person(const std::string &name, int age) : m_name(name), m_age(age) {} //
~Person(){}
void show() {
std::cout << m_name << " " << m_age << std::endl;
}
private:
std::string m_name;
int m_age;
};
int main()
{
// 栈上创建
Person p0; // 调用无参构造函数
Person p1("tom", 18); // 调用有参构造函数
Person p2("tony", 19); // 调用有参构造函数
p0.show();
p1.show();
p2.show();
// 堆上创建
Person *p3 = new Person(); // 调用无参构造函数
Person *p4 = new Person("zhangsan", 18);
p3->show();
p4->show();
delete p3;
delete p4;
return 0;
}
实例2:
// 一个计数器类的示例,它包含两个数据成员(value 和 step),
// 以及三个构造函数(无参构造函数、带一个参数的构造函数、带两个参数的构造函数)和一个成员函数(increment)。
#include <iostream>
#include <string>
class Counter {
public:
// 无参构造函数
Counter() : m_value(0), m_step(1) {}
// 带一个参数的构造函数
Counter(int value) : m_value(value), m_step(1) {}
// 带两个参数的构造函数
Counter(int value, int step) : m_value(value), m_step(step) {}
// 增值函数
void increment() {
m_value += m_step;
}
// 获取当前计数器值
// const关键字修饰getValue()函数,表示该成员函数不会修改对象的状态
// const成员函数不会修改类的成员变量,函数内部的任何修改都是暂时的
// 只在函数执行期间有效,并不会影响到类的成员变量
// 使用const修饰表示该函数是一个常量成员函数
// 当使用该函数时,编译器会对成员函数进行限制,进制修改对象的状态
// 常量成员函数可以被`const`对象和非`const`对象调用,
// 这里的`const`对象指的是通过`const`指针或引用传递的对象。
// 如果在常量成员函数中尝试修改对象的状态或调用非`const`成员函数,则会导致编译错误。
// 因此,常量成员函数一般用于访问或查询类的状态信息,而不涉及修改状态。
int value() { return 3;}
void setValue(int val) { m_value = val; }
int getValue() const {
// 在常量成员函数中调用非常亮成员函数违反const成员函数的规则
// 由于 `getValue` 函数被声明为常量成员函数,因此它不能修改对象的状态,
// 而 `value` 函数没有被声明为常量成员函数,它可以修改对象的状态。
// 在常量成员函数中调用非常量成员函数会导致编译器错误地认为该函数可能会修改对象的状态,因此会发出警告。
// setValue(3);
// return value();
// 在常量成员函数中不能修改对象的成员变量,因为常量成员函数被定义为不会修改对象的状态,编译器会禁止在其中修改对象的成员变量。
// m_value = 3;
return m_value;
}
// 获取步长
int getStep() const {
return m_step;
}
private:
int m_value;
int m_step;
};
int main()
{
Counter c1; // 使用无参构造函数创建计数器对象
std::cout << "c1 value : " << c1.getValue() << std::endl;
c1.increment();
std::cout << "c1 value after increment : " << c1.getValue() << std::endl;
Counter c2(10);
std::cout << "c2 value : " << c2.getValue() << std::endl;
c2.increment();
std::cout << "c2 value after increment : " << c2.getValue() << std::endl;
Counter c3(10, 2);
std::cout << "c3 value : " << c3.getValue() << std::endl;
c3.increment();
std::cout << "c3 value after increment : " << c3.getValue() << std::endl;
return 0;
}
拷贝构造:
// 拷贝构造函数是一种特殊的构造函数,它用于通过拷贝一个已有对象创建一个新对象。
// 在 C++ 中默认提供一个拷贝构造函数,如果类需要提供非平凡的拷贝构造函数,需要自行编写。
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
// 场景1:类中成员变量只有内置数据类型
class A {
public:
A(int val = 0) : m_val(val) {} // 普通构造函数
A(const A & obj) : m_val(obj.m_val) {} // 拷贝构造函数
void print() {
std::cout << "A(" << m_val << ")\n" << std::endl;
}
private:
int m_val;
};
// 场景2:类中成员变量包含指针数据类型
class B {
public:
// 普通构造函数
B(int *ptr = nullptr) : m_ptr(ptr) {}
// 拷贝构造函数
B(const B &obj) : m_ptr(new int(*obj.m_ptr)) {}
// 析构函数
~B() {delete m_ptr;}
void setValue(int val) const {
// 在这个函数中,使用了 `const` 修饰函数本身,表示该函数不会修改对象的非 `mutable` 成员变量。
// 但是,由于 `m_ptr` 是一个指针类型的成员变量,`const` 修饰的是指针本身的常量性,而非指针所指向的对象。
// 即使指针本身是常量,也可以通过指针修改它所指向的对象的值,因此在这个函数中可以修改对象的成员变量。
// 如果要避免在 `const` 修饰的函数中修改对象的成员变量,可以使用 `mutable` 关键字对成员变量进行修饰。
// 被 `mutable` 修饰的成员变量可以在 `const` 成员函数中被修改,
// 因为 `mutable` 表示该成员变量可以被视为非常量成员变量。
*m_ptr = val;
// 以下使用方式破坏了m_ptr的常量属性
// m_ptr = nullptr;
// m_ptr是一个常量,常量属性并没有变化,只是修改了m_ptr指向对象的值
std::cout << "m_ptr : " << m_ptr << std::endl;
*m_ptr = 5;
std::cout << "m_ptr : " << m_ptr << std::endl;
*m_ptr = 7;
std::cout << "m_ptr : " << m_ptr << std::endl;
*m_ptr = val;
}
void print() const {
std::cout << "B(" << *m_ptr << ")\n" << std::endl;
}
private:
int *m_ptr;
};
int main()
{
A a(10);
// 调用拷贝构造函数
A a1 = a;
a.print();
a1.print();
B b(new int(20));
// 调用拷贝构造函数
B b1 = b;
b.print(); // 输出 B(20)
b1.print(); // 输出 B(20)
b.setValue(30);
b.print(); // 输出 B(30)
b1.print(); // 输出 B(20)
return 0;
}
// 在 C++ 中,一个类可以拥有多个构造函数,但这些构造函数都是独立的,互不影响。
// 使用其中一个构造函数创建对象时,其他构造函数不会被自动调用或生效。
// 每个构造函数都有它自己的参数列表,它们可以用不同的参数顺序或不同的参数类型创建不同的对象。
// 但是,这些对象之间并不会互相干扰,它们仍然是独立的对象。
// 当你创建一个对象时,只有一个构造函数会被调用。这个构造函数的参数列表必须匹配你传入的参数。
// 如果你在创建对象时没有传入任何参数,则匹配参数列表为空的默认构造函数。
// 需要注意的是,在一个类中,如果有一个或多个构造函数需要在其他构造函数执行前调用,
// 可以使用初始化列表对数据成员进行初始化,以达到复用代码的目的。
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
class Person {
public:
Person() {
m_age = 18;
}
Person(int id, const std::string &name) {
m_id = id;
m_name = name;
// const修饰的成员为常量成员,不可以直接复制,需要通过构造函数参数表的形式进行初始化
// m_heigt = 175;
// m_gender = "man";
}
// 在 C++ 中,一个类可以拥有多个构造函数,但这些构造函数都是独立的,互不影响。
// 使用其中一个构造函数创建对象时,其他构造函数不会被自动调用或生效。
// 每个构造函数都有它自己的参数列表,它们可以用不同的参数顺序或不同的参数类型创建不同的对象。
// 但是,这些对象之间并不会互相干扰,它们仍然是独立的对象。
// 当你创建一个对象时,只有一个构造函数会被调用。这个构造函数的参数列表必须匹配你传入的参数。
// 如果你在创建对象时没有传入任何参数,则匹配参数列表为空的默认构造函数。
// 需要注意的是,在一个类中,如果有一个或多个构造函数需要在其他构造函数执行前调用,
// 可以使用初始化列表对数据成员进行初始化,以达到复用代码的目的。
void getAge() {
std::cout << "age = " << m_age << std::endl;
}
void getName() {
std::cout << "age = " << m_name << std::endl;
}
private:
int m_id;
std::string m_name;
int m_age;
// const 修饰的成员必须初始化,否则编译器报错
// const int m_heigt;
// const string m_gender;
};
int main()
{
// 使用其中一个造函数创建对象时,其他构造函数不会生效
Person zs(123, "zhangsan");
zs.getAge();
zs.getName();
// 使用其中一个造函数创建对象时,其他构造函数不会生效
Person ls;
ls.getAge();
ls.getName();
return 0;
}
// 有时候数据成员必须初始化,如const修饰的成员常量,还有一些引用也是必须要初始化的。
// 但不能在构造函数中复制,需要使用初始化列表的方式给数据成员赋值。
// 声明时不需要加初始化列表,初始化列表加在函数实现的时候。
// c语言中被const修饰的变量是只读属性,不能直接修改
// c++中const修饰的变量会修改变量属性,成为常量,不能被修改。
// 有时候数据成员必须初始化,如const修饰的成员常量,还有一些引用也是必须要初始化的。
// 但不能在构造函数中复制,需要使用初始化列表的方式给数据成员赋值。
// 声明时不需要加初始化列表,初始化列表加在函数实现的时候。
// c语言中被const修饰的变量是只读属性,不能直接修改
// c++中const修饰的变量会修改变量属性,成为常量,不能被修改。
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
class Person {
public:
// const 修饰的成员必须初始化,否则编译器报错
Person(int height, const std::string &gender);
void getHeight() {
std::cout << "height = " << m_heigt << std::endl;
}
void getGender() {
std::cout << "gender = " << m_gender << std::endl;
}
private:
const int m_heigt;
const string m_gender;
};
// 声明时不需要加初始化列表,初始化列表加在函数实现的时候。函数实现一般放到类体外部
Person::Person(int height, const std::string &gender) : m_heigt(height), m_gender(gender) {}
int main()
{
Person ww(175, "xiaoming");
ww.getHeight();
ww.getGender();
return 0;
}