普通成员函数必须在类中声明,声明方式与声明普通函数的方式一样
class People
{
public:
void getWeight();
void getHeight(); //成员函数
};
成员函数的定义有两种方式:
1)、在类中定义,此时该函数默认为内联函数;
2)、在类外定义,但要使用作用域限定符(::)
class Rectangle
{
private:
float height;
float width; //数据成员
public:
float getWidth()
{
return width; //类中定义,默认为内联函数
}
float getHeight();
};
//类外定义
float Rectangle::getHeight()
{
return height;
}
1)、通过对象使用成员运算符(.)调用;
2)、通过对象指针使用间接成员运算符(->)调用
1)、自定义构造函数(可有参数也可以没有参数);
2)、默认构造函数;
3)、拷贝构造函数
当没有自定义构造函数时,编译器会提供一个默认的构造函数;当自定义构造函数后,编译器不再提供默认构造函数
1)、构造函数没有返回值和声明类型;
2)、构造函数名与类名相同;
3)、构造函数可以有参数也可以没有参数;
4)、构造函数的函数体可以为空也可以不为空;
5)、构造函数可在类中定义也可在类外定义;
6)、构造函数不能声明为const型的;
7)、构造函数的参数可以有默认值;
8)、类可以有多个构造函数
用来初始化对象的数据成员
构造函数在创建对象时自动调用
1)、显式调用
2)、隐式调用
1)、在类中定义;
2)、可在类外定义,此时需要使用作用域限定符(::)
1)、在函数体内一一赋值初始化;
2)、采用初始化列表初始化
赋值初始化示例:
#include
using namespace std;
class Student
{
private:
int m_age;
float m_score;
public:
Student() { }
Student(int age, float score)
{
m_age = age;
m_score = score;
}
void show()
{
cout << "age = " << m_age
<< " score = " << m_score
<< endl;
}
};
int main()
{
Student stu = Student(16, 90); //显式调用
stu.show();
Student stu1(18, 96); //隐式调用
stu1.show();
return 0;
}
初始化列表示例
#include
using namespace std;
class Student
{
private:
int m_age;
float m_score;
public:
Student(int age, float score) : m_age(age), m_score(score)
{
}
void show()
{
cout << "age = " << m_age
<< " score = " << m_score
<< endl;
}
};
int main()
{
Student stu = Student(16, 90); //显式调用
stu.show();
Student stu1(18, 96); //隐式调用
stu1.show();
return 0;
}
className(argument-list)
{
... //
}
className()
{
}
编译器提供的默认构造函数不接受任何参数,函数体也不执行任何操作;
1)、给已有的构造函数的所有参数提供默认值;
2)、通过函数重载定义一个没有参数的构造函数
默认构造函数可在类中定义也可在类外定义,在类外定义时需要使用作用域限定符(::)
例如:
class Rectangle
{
private:
float height;
float width;
public:
Rectangle(float w = 0, float h = 0); //默认构造函数定义方式1
Rectangle() //默认构造函数定义方式2
{
height = 0;
width = 0;
}
};
1)、如果在类中没有显式定义构造函数,则C++自动提供默认构造函数;
2)、如果显式定义了构造函数,则C++不会再提供默认构造函数,必须为类提供一个默认构造函数;
3)、默认构造函数只能有一个;
4)、设计类时,通常应提供对所有类成员做隐式初始化的默认构造函数;
5)、默认构造函数初始化对象时对象的初始值是未知的
有了默认构造函数(没有参数或所有参数都有默认值),在创建对象的时候就不必初始化对象
#include
using namespace std;
class Student
{
private:
int m_age;
float m_score;
public:
Student(){}
Student(int age, float score)
{
m_age = age;
m_score = score;
}
void show()
{
cout << "age = " << m_age
<< " score = " << m_score
<< endl;
}
};
int main()
{
Student stu; //有了默认构造函数就可以先定义对象再进行初始化,否则就必须在定义的同时进行初始化: Student stu = Student(16, 90);
stu = Student(16, 90);
stu.show();
return 0;
}
类 T 的拷贝构造函数是非模板构造函数,其首个形参为 T&、const T&、volatile T& 或 const volatile T&,而且要么没有其他形参,要么剩余形参均有默认值
当没有自定义拷贝构造函数时,编译器会提供一个默认的拷贝构造函数;当自定义拷贝构造函数后,编译器不再提供默认拷贝构造函数
拷贝构造函数分为两类:
1)、默认拷贝构造函数
2)、自定义拷贝构造函数
逐个复制非静态成员的值
拷贝构造函数在以下三种情况下调用:
1)、用类的一个对象初始化另一个对象;
2)、将对象作为实参传递给一个非引用类型的形参(即按值传递);
3)、函数返回对象
函数原型
className(const className &);
拷贝构造函数说明:
1)、该函数接收一个指向类类型的引用作为参数;
2)、当没有自定义拷贝构造函数时,编译器会提供一个默认的拷贝构造函数作为其类的非 explicit 的 inline public 成员;当自定义拷贝构造函数后,编译器不再提供默认拷贝构造函数;
3)、默认拷贝函数对成员的复制称之为浅拷贝
什么情况下需要自定义拷贝构造函数?
当类中有需要用new来分配存储空间的成员时,需要自定义拷贝构造函数,此时的拷贝称之为深拷贝
#include
#include
using namespace std;
class Str
{
public:
char *p;
public:
Str(char *s)
{
int len = strlen(s);
p = new char[len + 1];
strcpy(p, s);
cout << "调用构造函数" << endl;
}
Str(const Str &s)
{
int len = strlen(s.p);
p = new char[len + 1];
strcpy(p, s.p);
cout << "调用拷贝构造函数" << endl;
}
~Str()
{
if (p != NULL)
{
delete [] p;
}
cout << "调用析构函数" << endl;
}
};
int main()
{
char s[] = "Hello";
Str s1(s);
Str s2 = s1;
cout << s1.p << endl;
cout << s2.p << endl;
return 0;
}
1)、自定义的析构函数;
2)、默认的析构函数
当没有自定义析构函数时,编译器会提供一个默认的析构函数;当自定义析构函数后,编译器不再提供默认拷贝构造函数
1)、析构函数声明时须在函数名前面加上~;
2)、析构函数没有参数,故不能重载,故类只能有一个析构函数;
3)、析构函数没有返回值和声明类型;
4)、若在类中没有显式定义析构函数,C++会自动提供一个默认的析构函数;若显式定义了析构函数,则不会再提供默认的析构函数;
5)、若某个类作为基类,则一般将析构函数声明为虚析构函数
6)、析构函数可以声明为纯虚,例如对于需要声明为抽象类,但没有其他可声明为纯虚的适合函数的基类;
7)、析构函数可在类中定义也可在类外定义,在类外定义时需要使用作用域限定符(::)
析构函数用来销毁对象和为对象的数据成员使用new分配的内存空间
以下情况下,析构函数是由编译器决定调用
1)、若创建的是静态存储类对象,则析构函数在程序运行结束时被自动调用;
2)、若创建的是自动存储类对象,则析构函数在程序执行完对象所在的代码块时被自动调用;
3)、若对象是通过new创建的,则析构函数在使用delete释放对象占用的内存时被自动调用
4)、若创建的是临时对象,则程序在结束对该对象的使用时调用析构函数
函数原型
~className()
{
}
~className()
{
}
virtual ~className() //类作为基类时的通常设为virtual
{
}
示例:
#include
using namespace std;
class Student
{
public:
Student(){}
~Student(){cout << "对象被销毁" << endl;}
};
int main()
{
{
Student stu;
Student *sp = new Student;
delete sp;
}
return 0;
}
运行结果:
对象被销毁
对象被销毁
在以下情况下需要自定义的析构函数;
1)、当类中有需要用new来分配存储空间的成员时;
2)、当某个类作为另一个类的基类
1)、自定义的赋值运算符
2)、编译器提供的默认的赋值运算符
当没有自定义赋值运算符时,编译器会提供一个默认的赋值运算符;当自定义赋值运算符后,编译器不再提供默认赋值运算符
与拷贝构造函数相似,赋值运算符也对非静态成员进行逐个复制,如果成员本身是类对象,则程序将使用为这个类定义的赋值运算符来复制该成员,但静态数据不受影响
将已有的对象赋给另一个对象时
MyClass foo;
MyClass bar (foo); // 初始化对象,调用拷贝构造函数
MyClass baz = foo; // 初始化对象,调用拷贝构造函数
foo = bar; // bar已经被初始化: 调用赋值运算符
函数原型
className & operator=(const className &);
什么时候需要自定义赋值运算符
1)、用非类类型的值为类类型的对象赋值时(当然,这种情况下我们可以不提供相应的赋值运算符重载函数,而只提供相应的构造函数);
2)、当类中含有指针成员,将同一个类的一个对象赋值给另一个对象时
className (className &&);
类 T 的移动构造函数是非模板构造函数,其首个形参是 T&&、const T&&、volatile T&& 或 const volatile T&&,且无其他形参,或剩余形参均有默认值
拷贝构造函数中,对于指针要采用深拷贝,而移动构造函数中,对于指针采用浅拷贝。类中有指针时浅拷贝之所以危险,是因为两个指针共同指向同一片内存空间,若将第一个指针释放,则另一个指针的指向就合法了。
同拷贝构造函数
若不对类提供任何用户定义的移动构造函数,且下列各项均为真:
1)、没有用户声明的复制构造函数;
2)、没有用户声明的复制赋值运算符;
3)、没有用户声明的移动赋值运算符;
4)、没有用户声明的析构函数;
则编译器将声明一个移动构造函数,作为其类的非 explicit 的 inline public 成员
#include
#include
using namespace std;
class Str
{
public:
char *p;
public:
Str(char *s)
{
int len = strlen(s);
p = new char[len + 1];
strcpy(p, s);
cout << "调用构造函数" << endl;
}
Str(Str &&s) //移动构造函数
{
p = s.p;
s.p = nullptr;
cout << "调用移动构造函数" << endl;
}
~Str()
{
delete [] p;
cout << "调用析构函数" << endl;
}
};
int main()
{
char s[] = "Hello";
Str s1(s);
Str s2(move(s1));
cout << s2.p << endl;
return 0;
}
className &operator=(className &&);
同赋值运算符
执行与移动构造函数相同的工作
若不对类类型(struct、class 或 union)提供任何用户定义的移动赋值运算符,且下列各项均为真:
没有用户声明的复制构造函数;
没有用户声明的移动构造函数;
没有用户声明的复制赋值运算符;
没有用户声明的析构函数;
隐式声明的移动赋值运算符不会被定义为弃置,
(C++14 前)
则编译器将声明一个移动赋值运算符,作为其类的 inline public 成员
#include
#include
using namespace std;
class Str
{
public:
char *p;
public:
Str()
{
}
Str(char *s)
{
int len = strlen(s);
p = new char[len + 1];
strcpy(p, s);
cout << "调用构造函数" << endl;
}
Str(Str &&s)
{
p = s.p;
s.p = nullptr;
cout << "调用移动构造函数" << endl;
}
Str &operator=(Str &&s)
{
p = s.p;
s.p = nullptr;
cout << "调用移动赋值运算符" << endl;
}
~Str()
{
delete [] p;
cout << "调用析构函数" << endl;
}
};
int main()
{
char s[] = "Hello";
Str s1(s);
Str s3;
Str s2(move(s1));
s3 = move(s2);
cout << s3.p << endl;
return 0;
}
static type functionName(arguments-list)
{
... //函数体
}
1)、静态成员遵循类成员访问规则(私有、保护、公开);
2)、静态成员函数不关联到任何对象,即使不定义类的任何对象它们也存在,它们无 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数);
3)、静态成员函数不能为 virtual、const 或 volatile;
4)、静态成员函数的地址可以存储在常规的函数指针中,但不能存储于成员函数指针中;
5)、静态数据成员不能为 mutable;
6)、在命名空间作用域中,若类自身具有外部连接(即不是无名命名空间的成员),则类的静态数据成员也具有外部连接。局部类(定义于函数内部的类)和无名类,包括无名类的成员类,不能拥有静态数据成员;
7)、静态成员函数在类外定义时不需要加关键字static
#include
using namespace std;
class Student
{
public:
Student(char *name, int age, float score);
void show();
public: //声明静态成员函数
static int getTotal();
static float getPoints();
private:
static int m_total; //总人数
static float m_points; //总成绩
private:
char *m_name;
int m_age;
float m_score;
};
int Student::m_total = 0;
float Student::m_points = 0.0;
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score)
{
m_total++;
m_points += score;
}
void Student::show(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
//定义静态成员函数
int Student::getTotal(){
return m_total;
}
float Student::getPoints(){
return m_points;
}
int main()
{
(new Student("小明", 15, 90.6)) -> show();
(new Student("李磊", 16, 80.5)) -> show();
(new Student("张华", 16, 99.0)) -> show();
(new Student("王康", 14, 60.8)) -> show();
int total = Student::getTotal();
float points = Student::getPoints();
cout<< "当前共有" << total
<< "名学生,总成绩是" << points
<< ",平均分是" << points / total << endl;
return 0;
}
运行结果:
小明的年龄是15,成绩是90.6
李磊的年龄是16,成绩是80.5
张华的年龄是16,成绩是99
王康的年龄是14,成绩是60.8
当前共有4名学生,总成绩是330.9,平均分是82.725
type functionName(arguments-list) const;
1)、常成员函数需要在声明和定义的时候在函数头部的结尾加上 const 关键字
2)、常对象只能调用常成员函数,普通对象可以调用所有成员函数;
3)、常成员函数中的const修饰的是该成员函数隐含的this指针,常成员函数中不能改变数据成员的值
4)、const成员函数既可使用const数据,也可使用非const数据,但都不能改变值
#include
#include
using namespace std;
class Test
{
private:
int m_a;
int m_b;
public:
Test(int a, int b) : m_a(a), m_b(b)
{
}
int getA() const
{
return m_a;
}
int getB() const
{
return m_b;
}
};
int main()
{
Test test(1, 2);
int a = test.getA();
int b = test.getB();
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
常成员函数中不得修改类中的任何数据成员的值
例如:
class Complex
{
public:
Complex();
Complex(int real, int image);
virtual ~Complex();
//编译出错,因为修改了数据成员,去掉const就正确
void Print() const
{
cout << "real = " << ++m_real << " image = " << ++m_image << endl;
}
//正确
void Print() const
{
cout << "real = " << m_real << " image = " << m_image << endl;
}
private:
int m_real;
int m_image;
};
如果需要析构函数,则一定需要拷贝构造函数和拷贝赋值操作符
在 C++11 标准中,为了支持移动语义,又增加了移动构造函数和移动赋值运算符,这样共有五个特殊的成员函数,所以又称为“C++五法则”;
三法则”是针对较旧的 C++89 标准说的,“五法则”是针对较新的 C++11 标准说的;为了统一称呼,后来人们干把它叫做“C++ 三/五法则”
1). 需要析构函数的类也需要拷贝构造函数和拷贝赋值函数;
2). 需要拷贝操作的类也需要赋值操作,反之亦然;
3). 析构函数不能是删除的;
4). 如果一个类有删除的或不可访问的析构函数,那么其默认和拷贝构造函数会被定义为删除的;
5). 如果一个类有const或引用成员,则不能使用合成的拷贝赋值操作
注:如果在类中没有显式定义以下函数,则编译器自动提供
1)、构造函数
2)、析构函数
3)、拷贝构造函数
4)、赋值运算符
5)、地址运算符
参考:
1、《C++ Primer Plus》
2、《Primer C++》
3、https://zh.cppreference.com
4、http://c.biancheng.net/view/2230.html
5、C++ 三五法则