1.类&对象
//Person类
class Person
{
public:
Person() {}; //默认构造
Person(int a,int b) {}; //有参构造
Person(const Person& p) {}; //拷贝构造
//成员函数
void init(int a,int b)
{
this->m_a = a;
this->m_b = b;
}
int get_max()
{
return max(this->m_a,this->m_b);
}
private:
int m_a; //成员变量
int m_b; //成员变量
}
Person p; //无参构造,对象p
Person p1(10,20); //有参构造,对象p1
Person p2(P1); //拷贝构造,对象p2
2.类访问修饰符
类成员被访问限定符限定访问范围,有三种访问修饰符,public公有权限,protected保护权限,private私有权限,类的默认访问权限是private权限。
class Person
{
//公有权限
public:
int m_a; //类内类外都可以访问
//保护权限
protected:
int m_b; //类外不能访问,派生类可以访问,类内和友元可以访问
//私有权限
private:
int m_c; //类外不能访问,派生类不能访问,类内和友元可以访问
}
protected权限和private权限,类外都不可以访问,两者的区别就在继承的时候出现。
3.构造函数&析构函数
3.1构造函数
- 类的构造函数是一种特殊的成员函数,在创建类的对象时就会自动调用。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
- 构造函数有三种,分别是默认构造,有参构造,拷贝构造。
构造函数的调用规则:默认情况下,编译器会给一个类提供四个成员函数,分别是默认构造,拷贝构造,(默认析构,重载operator= )只考虑构造函数 。如果用户自己定义了默认构造或者有参构造,那么编译器只会提供拷贝构造;如果用户自己定义了拷贝构造,那么编译器不会再提供构造函数
class Person
{
public:
Person() {cout << "Person的无参构造!" << endl;} //无参构造
Person(int a,int b) : m_a(a),m_b(b) //初始化列表
{cout << "Person的有参构造!" << endl;} //有参构造
Person(const Person& p) {this->m_a = p.m_a; this->m_b = p.m_b;} //拷贝构造(浅拷贝)
private:
int m_a;
int m_b;
}
3.2析构函数
- 类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
- 析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
- 析构函数最主要的一个作用就是,对象中开辟在堆区的成员,在对象被销毁时能够将堆区的数据也销毁(堆区需要手动释放数据)
class Person
{
public:
Person()
{
this->p = new int(10); //在堆区开辟一段内存4字节,数据是10
}
~Person()
{
cout << "Person析构函数调用!" << endl;
//释放堆区数据
if(p)
delete p;
p = nullptr;
}
private:
int m_a;
int* p; //将这个成员指针指向堆区数据
}
4.拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
(后面在深入理解对象模型中会再解释)
当用户没有自己定义拷贝构造时,编译器会提供一个完全赋值的构造函数(浅拷贝);如果类中成员变量有指针类型并且有动态内存分配(将数据开辟到堆区),那么就一定要手写一个拷贝构造(深拷贝)
重点:深拷贝与浅拷贝
class Person
{
public:
Person() {}; //无参构造
Person(int a,int b) : m_a(a),m_p(new int(b)) {}; //有参构造
//编译器提供的拷贝构造,浅拷贝
//完全的简单赋值,当有动态内存分配时就会出现问题。
/*对象p中的m_p这个指针指向的是一个堆区地址,此时将这个地址赋值给这个对象的m_p这个指针
此时有两个对象的成员指针指向同一个堆区地址,当这两个对象被销毁时,其在堆区开辟的内存要被释放
但是两个对象中的成员指针却指向的是同一个堆区地址,就会造成同一个堆区地址被重复释放,
因为一旦被释放一次过后该内存就成了非法内存,就没有权限再去操作了
这就是浅拷贝带来的问题:堆区内存重复释放
*/
//Person(const Person& p) {m_a = p.m_a;m_p = p.m_p;}
//解决办法:深拷贝
Person(const Person& p)
{
this->m_a = p.m_a;
this->m_p = new int(*p.m_p); //在堆区重新开辟一段内存,存放的数据一样,但不是同一块地址
}
~Person()
{
if(m_p)
delete m_p;
m_p = nullptr;
cout << "Person的析构函数" << endl;
}
private:
int m_a;
int* m_p;
}
5.友元
- 友元可以分为全局函数友元,成员函数友元,类友元
- 友元可以访问类中保护权限和私有权限的成员
- 友元需要用到关键字friend
class boy; //先声明一下这个类
class girl;
class Person
{
//声明友元
friend void func1(); //这个函数就可以访问该类protected和private权限的成员了
friend class boy; //这个类作为该类的友元
friend void girl::func3(Person& p1); //类girl中的成员函数func3作为该类的友元
public:
Person(){};
protected:
int m_c; //类内,友元,派生类可以访问
private:
int m_a; //类内,友元可以访问
int m_b;
}
//全局函数做友元
void func1()
{
Person p1;
p1.m_a = 10;
p1.m_c = 20;
}
//类做友元
class boy
{
public:
void func2(Person& p1)
{
p1.m_a = 10;
p1.m_c = 20;
}
}
//类成员函数做友元
class girl
{
public:
void func3(Person& p1)
{
p1.m_a = 10;
p1.m_c = 20;
}
}
6.对象模型
类是一个抽象的定义,只定义一个类并不会占用内存,只有创建具体的对象时,才会为其分配内存。类只是抽象出来的
-
类成员
一个类中有四种成员,分别为非静态成员变量,非静态成员函数,静态成员变量和静态成员函数。其中只有非静态的成员才属于类
一个类或者一个类对象的所占内存大小是多少?
对象模型:每一个实例化的对象,属于自己独有的只是非静态的成员变量,所以每一个类/对象的大小就是其中非静态成员变量的大小(内存对齐)。而对于非静态的成员函数,只存储一份,每个对象调用的都是同一份,那么每个非静态成员函数又怎么知道是哪个对象调用的自己呢?这就用到了this指针。对于静态成员变量和函数,它们不属于类和对象(静态成员函数内部没有this指针)。注意,一个空类/实例化的对象内存是1B -
this指针
每个非静态成员函数内都有一个隐藏的this指针,通过this指针编译器就知道是哪个对象在调用这个成员函数。哪个对象调用的这个成员函数,这个成员函数内的this指针就指向那个调用的对象。 使用return *this;返回调用函数的这个对象 如果是返回引用,那就是返回这个对象本体;如果返回值,那就是拷贝构造,返回这个对象的副本。(链式编程思想)this指针的本质:this指针的本质是一个指针常量(
this == person* const this
)也就是说this是一个常量,一个指针类型的常量。this指向的地址不可以改变,地址内的数据可以改变。哪个对象调用的这个函数,这个函数内的this指针就指向谁,不能改变指向。 -
const修饰成员函数(常函数和常对象)
1.成员函数后面+const 是常函数:void func() const
常函数内不能修改非静态成员变量 。 如果成员变量前加mutable
,则对于常函数和常对象都可以修改。
常函数+const的本质:本来this
指针为person* const this
,函数加上const
成为常函数后,this指针变为const person* const this
;此时this指向的地址不可以改变,地址内的数据也不可以修改。
2.常对象:const person p
;//p为常对象 常对象只能调用常函数 也可以修改mutable关键字的变量
#include <iostream>
using namespace std;
//静态成员变量和静态成员函数作为类成员
class Person
{
public:
Person() {
}
Person(int a,int b) : m_a(a),m_b(b)
{
cout << "Person有参构造" << endl;
}
~Person()
{
cout << "Person析构函数" << endl;
}
static void func()
{
//静态成员函数,不属于类,在全局区,所有成员对象共享一个函数,且静态成员函数只能访问静态成员变量
//静态成员函数中是没有this指针的
m_c = 600;
}
int m_a;
int m_b;
static int m_c; //静态成员变量,不属于类,在全局区 所有成员对象共享一份数据
};
//静态成员变量 类内声明类外初始化
int Person::m_c = 10;
void test01()
{
Person p(100,200);
cout << p.m_a << endl;
cout << p.m_b << endl;
cout << "static m_c = " << p.m_c << endl;
cout << "static m_c = " << Person::m_c << endl; //静态成员变量 通过类名作用域使用
cout << "sizeof(Person) = " << sizeof(p) << endl; //sizeof = 8,说明静态成员变量不在类上
Person p2;
p2.m_c = 50;
cout << "p.m_c = " << p.m_c << " p2.m_c = " << p2.m_c << " person::m_c = " << Person::m_c << endl; //全部都被修改成了50
Person::func();
cout << "p.m_c = " << p.m_c << " p2.m_c = " << p2.m_c << " person::m_c = " << Person::m_c << endl; //全部都被修改成了600
}
/*-----------------------------------------------------*/
class Base
{
public:
Base() {
}
Base(int a,int b) : m_a(a),m_b(b)
{
cout << "Base有参构造函数" << endl;
}
~Base()
{
cout << "Base析构函数" << endl;
}
//成员函数和成员变量分开存储,每个类对象创建时会创建出一份成员变量,但成员函数只有一份,如何区分是哪个对象在调成员函数,this指针
//每个非静态成员函数中有一个this指针(包括构造函数和析构函数),this指针指向调用该成员函数的对象
//this指针可以拿到所有该对象所属的变量和函数,但不能拿到静态的成员变量和函数,因为静态成员变量和函数不属于类
void func()
{
this->m_a = 100;
this->m_b = 200;
cout << "m_a = " << this->m_a << " m_b = " << this->m_b << endl;
}
Base copy_Base()
{
this->m_a += 10;
this->m_b += 20;
return *this; //返回调用这个函数的对象,这里返回值是值返回,所以返回的是该对象的一个拷贝 这里会调用匿名拷贝构造
}
Base& re()
{
this->m_a += 10;
this->m_b += 20;
return *this; //返回调用这个函数的对象,这里返回值是引用返回,所以返回的是这个对象本身
}
int m_a;
int m_b;
};
class Base1
{
public:
};
void test02()
{
Base1 b1;
cout << "sizeof(Base1) = " << sizeof(Base1) << endl; //空类的对象大小是1
Base b(10,20);
b.func(); //b这个对象调用非静态成员函数func,那么func内的this指针就指向b
cout << "sizeof(Base) = " << sizeof(Base) << endl;
//this指针
Base bb(10,20);
bb.copy_Base().copy_Base().copy_Base(); //第一次调用结束后返回的是拷贝出来的一个bb副本,后面全是返回该次调用的拷贝
cout << "m_a = " << bb.m_a << " m_b = " << bb.m_b << endl; //m_a=20,m_b=40;说明返回的是拷贝,不是返回的bb本体
Base bb2(10,20);
bb2.re().re().re(); //返回的是引用,所以*this返回的是本身
cout << "m_a = " << bb2.m_a << " m_b = " << bb2.m_b << endl; //m_a=40,m_b=80;说明返回的是本身
//this指针本质:this指针是一个指针常量,指针是一个常量,指针的值(指针指向的地址)不可以改变,地址内的数据可以改变 Base* const this = &bb;
}
/*-------------------------------------------------*/
class Human
{
public:
Human(){
}
Human(int a,int b,int c) : m_a(a),m_b(b),m_c(c)
{
cout << "Human有参构造函数" << endl;
}
~Human()
{
cout << "Human析构函数" << endl;
}
//常函数
void func() const
{
//this指针变从Human* const this 变为 const Human* const this
//this指针指向的地址和地址内的数据都不可以改变
//this->m_a = 200; error 常函数内不可以修改非静态成员变量
this->m_c = 200; //如果非静态成员变量声明时前面加关键字mutable,那么常函数和常对象内依旧可以修改
m_d = 300; //常函数内可以修改静态成员变量,因为this指针并不会拿到静态成员变量
}
int m_a;
int m_b;
mutable int m_c; //加关键字mutable,即使是常函数和常对象依旧可以访问修改此变量
static int m_d;
};
int Human::m_d = 10;
void test03()
{
Human h(10,20,30);
h.func();
cout << "m_a = " << h.m_a << " m_b = " << h.m_b << " m_c = " << h.m_c << " m_d = " << h.m_d << endl;
//常对象
const Human h1(10,20,30);
h1.func(); //常对象只能调用常函数
//h1.m_a = 100; error 常对象不可以修改非静态成员变量
h1.m_c = 100; //但是,常对象可以修改用mutable关键字修饰的非静态成员变量
h1.m_d = 400; //常对象可以修改静态成员变量
}
int main()
{
//test01();
//test02();
test03();
return 0;
}