文章目录
封装的意义
- 将属性和行为作为一个整体,表现生活中的事物
- 类可以把属性和行为放在不同的权限下,加以控制
成员变量和成员函数
在C++中,成员变量和成员函数分开存储。计算类所占内存大小时,只有非静态成员变量才属于类的大小,其余都不计算在类的内部。
空类创建的对象占的字节数为1
类的成员变量
类对象的构造顺序:
- 分配内存,调用构造函数,隐式/显式地初始化各数据成员;
- 进入构造函数,在构造函数中执行赋值和计算。
普通成员变量
类的初始化方式:
- 传统初始化方式
Person(int a, int b, int c)
{
m_a = a;
m_b = b;
m_c = c;
}
- 初始化列表
Person(int a, int b, int c):m_a(a), m_b(b), m_c(c)
{
}
注:推荐使用初始化列表的方式,直接对成员变量进行初始化;而传统的初始化方式调用了默认构造函数,然后在构造函数中完成了赋值操作,所以体现出了效率的差异。
以下三种情况必须使用初始化列表:
- 初始化的成员变量是对象的情况
#include <iostream>
using namespace std;
class Test
{
public:
Test(int, int, int)
{
cout<<"Test"<<endl;
}
private:
int x;
int y;
int z;
};
class Mytest
{
public:
Mytest():test(1, 2, 3){
cout << "Mytest" << endl;
}
private:
Test test;
};
- 初始化const修饰的类成员变量 或引用类型的成员变量
class Test
{
public:
Test():a(10) { }
private:
const int a;
};
或者
class Test
{
public:
Test(int a):a(a) {}
private:
int& a;
};
- 子类初始化父类私有成员变量,只能通过初始化列表
class Test
{
public:
Test(){}
Test(int x):m_x(x) {}
void show() {cout<<m_x<<endl;}
private:
int m_x;
};
class Mytest:public Test
{
public:
Mytest():Test(110) {}
};
初始化成员列表使用注意事项:
构造函数列表初始化不是按照列表的顺序,而是按照变量的声明顺序。
例如:
#include <iostream>
using namespace std;
class Test
{
public:
Test(int i): m_j(i), m_i(m_j) { }
int get_i() const{
return m_i;
}
int get_j() const{
return m_j;
}
private:
int m_i;
int m_j;
};
int main()
{
Test test(10);
cout << test.get_i << endl << test.get_j << endl;
return 0;
}
输出结果:随机数 和 10
mutable 可变成员变量
一般const成员函数不能修改成员变量,但是只要成员变量被声明为mutable,则在const成员函数中也可以修改它。
mutable不能修饰静态成员变量
static 静态成员变量
静态成员变量的作用:
- 不管定义多少个类对象,静态成员变量都分配在全局数据区的内存,节省了存储空间;
- 当某变量需要修改时,只需要修改一次,所有对象的该变量都被修改了;
- 有一些变量和类相关而不是和对象相关,这些变量用静态成员变量。
使用:
class Rectangle
{
private:
static int count;
int high, width;
public:
Rectangle() {count++;}
Rectangle(int high_value, int width_value = 5);
~Rectangle() {count--;}
static int get_count() {return count;}
};
int Rectangle::count = 0;
注意:静态成员变量 需要类内声明,类外初始化 (其实是定义,分配内存)
static静态成员变量的特性:
- 生命周期
静态成员变量从类被加载到类被卸载,一直存在,在编译阶段就已经分配了内存;
普通成员变量从类对象被加载到类对象结束。 - 共享方式
静态成员变量是全类共享;
普通成员变量是每个对象单独占有。 - 存储位置
静态成员变量存储在静态全局区;
普通成员变量存储在堆区或栈区。 - 初始化位置
静态成员变量类外初始化;
普通成员变量类内初始化。
类的成员函数
按功能来分
构造函数
构造函数用来初始化类对象的数据成员,只要类的对象被创建,就会自动调用构造函数
构造函数的语法:
类名() { }
- 构造函数没有返回值,也不写void
- 构造函数名与类名相同
- 构造函数有参数列表和函数体(可以都为空)
- 一个类可以有多个构造函数,类似于函数重载,在参数类型和数量上要有不同
- 构造函数自动调用,无需手动调用
- 构造函数不能定义成const。
构造函数分类:
- 无参(默认)构造
Person()
{
cout<<"Person的无参构造"<<endl;
}
或者
Person(int a=10)
{
cout<<"Person的无参构造"<<endl;
}
- 有参构造
Person(int age)
{
m_age = age;
}
- 拷贝构造
Person(const Person &p)
{
m_age = p.age;
}
编译器会自动生成默认构造函数的情况:
- 当用户没有定义构造函数
- 在默认构造函数后面加=default,编译器会强行生成一个默认构造函数
Person()=default;
拷贝构造函数
通过拷贝构造函数实现类对象之间的拷贝过程
参数:
- X&
- const X&
- volatile X&
- const volatile X&
拷贝构造函数是一种特殊的构造函数,函数名称必须和类名称相同,第一个参数必须是本类型的一个引用变量。
注意:必须是引用类型,否则会构成递归调用。
默认拷贝构造函数
形式:
编译器一般会给我们自动生成默认拷贝构造函数,只是使用老对象的数据成员对新对象的数据成员进行赋值操作。一般代码如下 :
Rectangle(const Rectangle &r)
{
m_height = r.height;
m_width = r.width;
}
默认拷贝构造函数存在的问题:
-
静态成员的复制问题
默认拷贝构造函数不会处理静态成员变量,成员变量复制的时候不会复制静态成员变量。
比如一个类中静态成员变量来统计对象的个数,在用一个对象rec1复制出对象rec2;当输出此时的对象个数的时候,仍显示是1个。此外,在销毁对象的时候,由于类的析构函数会调用两次,此时的计数器将边为负数。
解决方法:自己定义拷贝构造函数,在拷贝构造函数中对计数器进行操作。 -
指针的复制问题(浅拷贝和深拷贝)
浅拷贝:指的是对对象进行拷贝的时候,只对对象中的数据成员进行简单的赋值。多数情况下浅拷贝已经能够很好地解决问题,但是当出现动态成员,浅拷贝就会出现问题。
解决方法 :
一是将拷贝构造函数设置为私有,防止默认拷贝构造函数的调用,这样拷贝的时候就会报错
二是在自定义的拷贝构造函数中执行深拷贝(在堆区重新申请空间,进行拷贝操作),有了自定义拷贝构造函数就不会有默认拷贝构造函数
构造函数调用规则
默认情况下,C++编译器至少给一个类添加三个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则:
- 如果定义了有参构造函数,则C++不会提供默认构造函数,但是会提供默认拷贝构造函数
- 如果定义了拷贝构造函数,则C++不会再提供其他构造函数。
析构函数
作用:释放对象使用的资源并销毁非static成员,不能重载,只能有唯一一个。
当析构函数未定义时,编译器自动生成,对象被销毁时自动调用。
类对象作为类成员时构造和析构顺序
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
例如
class A { }
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构顺序时谁先谁后?
A构造——B构造——B析构——A析构
当其他类对象作为本类成员,构造时先构造类对象,再构造自身;析构的顺序与构造相反
按特性来分
inline成员函数
为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。
定义在类中的成员函数默认都是内联的,如果在类定义时就在类内给出函数定义,那当然最好。如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上 inline,否则就认为不是内联的。
class A
{
int Foo(int a, int b) {} //内联函数
};
最好采用分文件编写:
//头文件
class A
{
public:
void Foo(int a, int b);
};
//定义文件
inline void A::Foo(int a, int b) { }
const成员函数
const成员函数本质上是隐式修改this指针的类型
this指针本质上是指针常量,指针的指向不能被修改。在类成员函数后面加const,修饰的是this指向,让指针指向的值不可以被修改。
常函数:
- 在成员函数后面加const构成常函数
- 常函数不能修改成员属性
- 常函数可以修改mutable修饰的成员变量
常对象:
- 声明对象前加const,称该对象为常对象
- 常对象只能调用常函数
static成员函数
静态成员函数为整个类所有,只能访问静态成员变量和静态成员函数。
类的this指针
- this指针是类的指针,指向对象的首地址
- 成员函数默认会隐式包含this指针形参
- 在成员函数中,对成员变量的调用都会默认转换为用this指针对成员变量的调用
- this指针只能在成员函数中使用,在全局函数、静态成员函数中都不能用this
类的友元
友元的目的:可以让一个函数或者类访问另一个类中的私有成员
全局函数作友元
class Building
{
friend void GoodGay(Building *building);
public:
Building()
{
this->m_sittingroom = "客厅";
this->m_bedroom = "卧室";
}
string m_sittingroom;
private:
string m_bedroom;
};
void GoodGay(Building *building)
{
cout << building->m_sittingroom << endl;
cout << building->m_bedroom << endl;
}
类作友元
class Building
{
friend class GoodGay;
public:
Building();
string m_sittingroom;
private:
string m_bedroom;
};
成员函数作友元
class Building;
class GoodGay
{
public:
GoodGay();
void visit();
void visit2();
private:
Building *building;
};
class Building
{
friend void GoodGay::visit();
public:
Building();
string m_sittingroom;
private:
string m_bedroom;
};
类的大小
- 类的非静态数据成员数据类型的大小之和;
- 由编译器额外加入的成员变量大小,用来支持语言的某些特性(比如存在虚函数,就会为该类生成一个虚函数表,并在该类的每个实例中添加一个指向虚函数表的指针,32位的4字节,64位的8字节)
- 为了优化存取效率,采用内存对齐;
- 类的大小与构造函数、析构函数和其他的成员函数无关;
- 空类的大小是1个字节。