vscode
控制台打印中文乱码,通过输入 chcp 65001
可以成功显示中文;
类的访问权限分为三种
Public
在类的内部可以访问,在类的外部也可以访问Protected
在类的内部可以访问,在类的外部不可以访问Private
在类的内部可以访问,在类的外部不可以访问
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public: // 公有成员,类的内部可以访问,外部也可以访问
string m_Name;
int m_Age;
private: // 私有成员,类的内部可以访问,外部不可以访问
int m_Weight;
public:
void show() // 公有方法,类的内部可以访问,外部也可以访问
{
cout << m_Name << ": " << m_Age << endl;
}
private:
void setName(string name) // 私有方法,类的内部可以访问,外部不可以访问
{
m_Name = name;
}
};
int main()
{
Animal a;
a.m_Name = "dog"; // 公有成员
a.m_Age = 18; // 公有成员
// a.m_Weight = 20; // 私有访问权限,在类的内部可以访问,在类的外部不可以访问
// a.setName("cat"); // 私有方法,在类的内部可以访问,在类的外部不可以访问;
a.show();
cout << a.m_Name << endl;
cout << a.m_Age << endl;
return 0;
}
如果我们自己不提供构造函数,编译器会为我们提供默认的构造函数和析构函数;
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
- 析构函数:主要作用在于对象销毁前系统的自动调用,执行一些清理工作;
构造函数语法: 类名(){}
- 构造函数,没有返回值,也不写
void
- 函数名称与类名称相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象的时候,会自动调用构造函数;无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
- 析构函数,没有返回值也不写
void
- 函数名称与类名相同,在名称前加上符号
~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构;无须手动调用,而且只会调用一次
#include <iostream>
#include <string>
using namespace std;
//构造函数分类
//按照参数分类:有参和无参,无参为默认构造函数
//按照类型分类:普通和拷贝构造函数
class Person
{
public:
// 构造函数
// 实例化对象会被调用
// 只会被调用一次
// 有参数,可以发生重载
Person()
{
cout << "无参构造函数调用" << endl;
} // 默认构造函数
Person(int a)
{ // 有参构造函数
age = a;
cout << "有参构造函数调用" << endl;
}
Person(const Person &p)
{ // 拷贝构造函数
age = p.age;
cout << "拷贝构造函数调用" << endl;
}
// 析构函数(没有参数,方法名和类名相同,在前面加上 ~)
// 只能被调用一次
// 不能发生重载
// 不需要手动调用,离开作用域后,编译器会自动调用,释放空间
~Person()
{
// 析构函数
cout << "析构函数调用" << endl;
}
int age;
};
// 括号法调用构造函数
void test01()
{
Person p; // 调用默认构造函数,默认构造函数不要添加()
// Person p4(); //不会调用默认构造函数,编译器会将下面一行当作方法声明 和 int func(); 是方法声明一样
Person p2(10); // 调用有参构造函数
cout << "p2.age = " << p2.age << endl;
Person p3(p2); // 拷贝构造函数
cout << p3.age << endl;
}
// 显式法
void test02()
{
Person p1;
Person p2 = Person(10); // 有参构造
Person p3 = Person(p2); // 拷贝构造函数
Person(10); // 匿名对象,在当前行执行结束后,系统会立即回收掉匿名对象
// 不要用拷贝构造函数,初始化匿名对象,编译器会认为 Person (p3) == Person p3; 对象重定义
// Person(p3);
}
// 隐式转换法
void test03()
{
Person p = 10; // 隐式调用 Person(int a); 这个构造函数等同于 Person p = Person(10);
Person p2 = p; // 隐式调用 Person(const Person& p) 拷贝构造函数
}
int main()
{
// test01();
// test02();
test03();
return 0;
}
调用构造函数时,可以使用隐式转换规则;在构造函数前加上 explict
关键字,可以禁止编译器使用隐式转换规则;
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
// 不加 explicit 可以使用隐式转换 Person p = 19;
// 加了后,就必须使用显式的方式去实例化对象
explicit Person(int age)
{
m_Age = age;
}
int m_Age;
};
int main()
{
// Person p = 19; //隐式转换 添加 explicit 这样声明会报错
Person p2(19); // 显式调用有参构造函数
cout << "p2.m_Age = " << p2.m_Age << endl;
return 0;
}
在不写任何构造函数的情况下,编译器默认会为我们提供以下三个构造函数
- 默认构造函数(空参数,空实现)
- 析构函数
- 拷贝构造函数(简单值拷贝)
#include <iostream>
#include <string>
using namespace std;
// 不写任何构造函数的情况下,编译器会默认提供三种构造函数
// 默认构造函数(空实现)
// 默认析构函数(空实现)
// 拷贝构造函数
class Person
{
public:
int m_Age;
};
int main()
{
Person p; // 编译器默认提供构造函数
p.m_Age = 19;
Person p2(p); // 编译器默认提供的拷贝构造函数
cout << "p.m_Age = " << p.m_Age << endl;
cout << "p2.m_Age = " << p2.m_Age << endl;
return 0;
}
-
提供了默认构造函数,编译器将不再提供默认构造函数,但提供拷贝构造函数和析构函数
-
提供了有参构造函数,编译器将不再提供默认构造函数,但提供拷贝构造函数和析构函数
-
提供了拷贝构造函数,编译器将不再提供默认构造函数,但提供析构函数
class Person
{
public:
//自定义了默认构造函数,编译器将不再提供默认构造函数,但提供拷贝构造函数
Person() {
}
//自定义了有参构造函数,编译器将不再提供默认构造函数,但提供拷贝构造函数
Person(int age) {
m_Age = age;
}
// 自定义了拷贝构造函数,编译器将不再提供默认构造函数
Person(const Person &p)
{
m_Age = p.m_Age;
}
int m_Age;
};
- 用一个类去初始化另一个对象时;
- 当函数的形参是类的对象时(值传递),引用传递不会调用拷贝构造函数;
- 当函数的返回值时类的对象时;
编译器提供的拷贝构造函数,只是做了简单的值拷贝;当类中存在指针变量时,编译器提供的拷贝构造函数会导致程序崩溃
#include <iostream>
#include <string>
using namespace std;
// 浅拷贝和深拷贝
// 存在在堆区开辟的内存空间,编译器提供的拷贝构造函数是简单的值拷贝
class Person
{
public:
Person(int age, int height)
{
m_Age = age;
m_Height = new int(height);
}
~Person()
{
if (m_Height != nullptr)
{
cout << m_Height << endl;
cout << "析构函数调用" << endl;
delete m_Height;
m_Height = nullptr;
}
}
int m_Age;
int *m_Height; // 指针变量
};
int main()
{
Person p(24, 160);
Person p2(p); // 调用编译器默认提供的拷贝构造函数
cout << "p.m_Age = " << p.m_Age << endl;
cout << "p.height = " << *p.m_Height << endl;
cout << "p2.m_Age = " << p2.m_Age << endl;
cout << "p2.height = " << *p2.m_Height << endl;
return 0;
}
存在在堆区开辟的内存空间,就一定要自己实现拷贝构造函数**;没有在堆区开辟对象的操作,不需要重写拷贝构造方法**;
class Person
{
public:
Person(int age, int height)
{
m_Age = age;
m_Height = new int(height);
}
//自己实现拷贝构造函数,避免两个指针指向同一块内存区域,从而导致该区域被释放两次
Person(const Person &p)
{
m_Age = p.m_Age;
m_Height = new int(*p.m_Height);
}
~Person()
{
if (m_Height != nullptr)
{
cout << m_Height << endl;
cout << "析构函数调用" << endl;
delete m_Height;
m_Height = nullptr;
}
}
int m_Age;
int *m_Height; // 指针变量
};
c11
提供了一个初始化对象的方法(赋初始值),相比传统的方式,更加简洁;
定义方式如下 类名(传入参数): 属性(参数1), 属性(参数2)... {}
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
// 传统赋值方法
// Person(int age, string name)
// {
// m_Age = age;
// m_Name = name;
// }
// 初始化列表
Person(int age, string name) : m_Age(age), m_Name(name)
{
}
int m_Age;
string m_Name;
};
int main()
{
Person p(24, "hello");
cout << "p.m_Age = " << p.m_Age << endl;
cout << "p.name = " << p.m_Name << endl;
return 0;
}
- 静态成员变量,所有对象共享同一份数据
- 编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数也是有访问权限的
访问方式:
- 通过对象访问
- 通过类名访问
#include <iostream>
#include <string>
using namespace std;
// 静态成员变量
class Person
{
public:
// 静态成员变量,所有对象共享同一份数据
// 编译阶段分配内存
// 类内声明,类外初始化
static int m_Age;
// 静态成员变量也是有访问权限的
private:
static int m_B;
};
int Person::m_Age = 10;
int Person::m_B = 20;
void test1()
{
Person p;
cout << p.m_Age << endl;
Person p2;
p2.m_Age = 200;
cout << p.m_Age << endl;
cout << p2.m_Age << endl;
}
void test2()
{
// 静态成员变量不属于某个对象,所有的对象共享同一份数据
// 因此静态成员变量有两种访问方式
// 1. 通过对象访问
Person p;
cout << p.m_Age << endl;
// 2. 通过类名访问
cout << Person::m_Age << endl;
}
void test3()
{
// cout << Person::m_B << endl;
}
int main()
{
// test1();
test2();
// tets3();
return 0;
}
- 所有对象共享共一个函数
- 静态成员函数只能访问静态成员变量
- 静态成员函数也是有访问权限的
访问方式:
- 通过对象访问
- 通过类名访问
#include <iostream>
using namespace std;
// 静态成员函数
// 所有对象共享共一个函数
// 静态成员函数只能访问静态成员变量
class Person
{
public:
static void func()
{
m_A = 30;
// m_B = 200; // 静态成员函数只能访问静态变量 无法区分到底是哪个对象的 m_B;
cout << "static void func() " << endl;
}
static int m_A; // 静态成员变量
int m_B; // 非静态成员变量
// 静态成员函数也是有访问权限的
private:
static void func()
{
cout << "static void func" << endl;
}
};
int Person::m_A = 20;
void test1()
{
// 1 通过对象访问
Person p;
p.func();
// 2 通过类名访问
Person::func();
// Person::func2(); // 类外访问不到私有作用域的静态函数
}
int main()
{
test1();
return 0;
}
- 空对象占用对象的空间为1字节
- C++编译器会给每个空对象分配一个字节的空间,为了区分空对象占用的内存位置
- 非静态成员变量, 属于对象
- 静态成员变量,不属于对象
- 成员函数,不属于对象
- 静态成员函数,不属于对象
#include <iostream>
using namespace std;
// C++对象模型和this指针
// 成员和方法是分开存储的
class Person
{
};
class Person2
{
public:
int m_A; // 非静态成员变量,属于对象
};
class Person3
{
public:
int m_A;
static int m_B; // 静态成员变量
void func()
{ // 不属于对象上
}
static void func2()
{ // 不属于对象上
}
};
int Person3::m_B = 1;
void test1()
{
Person p;
// 空对象占用对象的空间为1字节
// C++编译器会给每个空对象分配一个字节的空间,为了区分空对象占用的内存位置
cout << sizeof(p) << endl;
}
void test2()
{
Person2 p;
cout << sizeof(p) << endl; // 4字节, 存在一个 int 类型
}
void test3()
{
Person3 p;
cout << sizeof(p) << endl; //4字节,存在一个非静态成员类型 int
}
int main()
{
// test1();
// test2();
test3();
return 0;
}
- this 指针指向被调用的成员函数所属的对象
- this 指针隐含在每一个非静态函数内的一种指针;
- 不需要定义,直接使用即可
主要用来解决两类问题
- 解决名称冲突
*this
返回对象本身
#include <iostream>
using namespace std;
// this指针
// this指针指向被调用的成员函数所属的对象
// this指针隐含在每一个非静态函数内的一种指针; 不需要定义,直接使用即可
class Person
{
public:
int age;
Person(int age)
{
// this 指针指向的是被调用的成员函数所使用的对象
this->age = age; // 解决名称冲突
}
// 返回对象本身
Person &addPerson(Person &p)
{
this->age += p.age;
// 返回对象本身
return *this;
}
};
// 解决名称冲突
void test01()
{
Person p(20);
cout << p.age << endl;
}
// 返回对象本身 *this
void test02()
{
Person p1(10);
Person p2(20);
// 链式编程
p2.addPerson(p1).addPerson(p1);
cout << p2.age << endl;
}
int main()
{
// test01();
test02();
return 0;
}
空指针也可以调用成员函数
#include <iostream>
using namespace std;
// 空指针也可以调用成员函数
class Person
{
public:
int m_Age;
void showName()
{
cout << "showName" << endl;
}
// 报错, 使用了成员函数,默认会传入一个 this 指针
void showAge()
{
cout << "showAge " << m_Age << endl; // this->m_Age this 是 nullptr
// cout << "showAge " << this->m_Age << endl;
}
};
// 空指针也可以调用成员函数
void test1()
{
Person *p = nullptr;
// p->showName();
p->showAge();
}
int main()
{
test1();
return 0;
}
常函数:
- 成员函数后加上
const
后,我们称这个函数为常函数 - 常函数内不可以修改成员属性
- 成员属性声明时加关键字
mutable
后,在常函数中依然可以修改
常对象:
- 声明对象前加上
const
称该对象为常对象 - 常对象只能调用常函数
全局函数可以访问类中的私有函数
class Building
{
//全局函数是 Building类的友元,可以访问私有成员
friend void goodGay(Building *building);
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
void goodGay(Building *building)
{
cout << "visit " << building->m_SittingRoom << endl;
cout << "visit " << building->m_BedRoom << endl; //访问类中的私有成员
}
void test01()
{
Building building;
goodGay(&building);
}
int main()
{
test01();
return 0;
}
类作为当前类的友元;friend class B
;类 B
作为声明中某个类的友元
// 类做友元
class Building; // 前向声明
class GoodGay
{
public:
GoodGay();
Building *building;
// 函数访问Building的公共成员和私有成员
void visit();
};
class Building
{
friend class GoodGay; // GoodGay 是 Building 的友元,可以访问私有权限
public:
Building(); // 在类外写成员函数
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
// 类外写成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
building = new Building;
}
void GoodGay::visit()
{
cout << "visit " << building->m_SittingRoom << endl;
cout << "visit " << building->m_BedRoom << endl;
}
void test01()
{
GoodGay goodGay;
goodGay.visit();
}
int main()
{
test01();
return 0;
}
class Building; // 前向声明
class GoodGay
{
public:
GoodGay();
Building *building;
// 函数访问Building的公共成员和私有成员
void visit();
void visit2(); // 只可以访问公共成员
};
class Building
{
// 成员函数做友元,告诉编译器,GoodGay 中的 visit() 是本类的友元函数,可以访问本类中的私有成员变量
friend void GoodGay::visit();
public:
Building(); // 在类外写成员函数
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
// 类外写成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
building = new Building;
}
void GoodGay::visit()
{
cout << "visit " << building->m_SittingRoom << endl;
cout << "visit " << building->m_BedRoom << endl; //友元函数可以访问私有成员
}
void GoodGay::visit2()
{
cout << "visit2 " << building->m_SittingRoom << endl;
// cout << "visit" << building->m_BedRoom << endl; 不可以访问,不是友元方法
}
void test01()
{
GoodGay goodGay;
goodGay.visit();
cout << "=================" << endl;
goodGay.visit2();
}
int main()
{
test01();
return 0;
}
内置数据类型,编译器知道该如何处理 + - * /
这些运算规则;如果是自定义类型,如果要实现相加,需要对运算符进行重载
通过自己写一个成员函数,可以实现两个自定义对象相加;
语法:class 类名: [public, protected, private] 继承类名
继承权限
一个原则:父类
private
权限的成员,在子类无法访问,也无法被继承
public
继承(公有继承):- 父类
public
权限成员,继承过来也是public
权限(类内部和外部都可以访问) - 父类
protected
权限成员,继承过来也是protected
权限(类内部可以访问,类外部无法访问)
- 父类
protected
继承(保护继承):- 父类
public
权限成员,继承过来是protected
权限(类内部可以访问,类外部无法访问) - 父类
protected
权限成员,继承过来也是protected
权限(类内部可以访问,类外部无法访问)
- 父类
private
继承(隐私继承):- 父类
public
权限成员,继承过来是private
权限(类内部可以访问,类外部无法访问) - 父类
protected
权限成员,继承过来是private
权限(类内部可以访问,类外部无法访问)
- 父类
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
int m_A;
private:
int m_B;
protected:
int m_C;
};
// public 继承,继承过来的 public 的成员,仍然是 public权限,继承过来的 protected 权限,仍然是 protected 权限
class Son1 : public Person
{
public:
void func()
{
m_C = 200;
m_A = 200;
// m_B = 200; //父类隐私的成员子类无法访问
}
};
// protected 继承,继承过来的 public 和 protected 成员权限统一都变成 protected权限
class Son2 : protected Person
{
public:
void fun()
{
m_C = 200;
m_A = 200;
// m_B = 200; //父类隐私的成员子类无法访问
}
};
// private 继承,继承过来的 public 和 protected 成员权限统一都变成 private 权限
class Son3 : private Person
{
public:
void fun()
{
m_C = 200;
m_A = 200;
// m_B = 100; //父类隐私的成员子类无法访问
}
};
void test01()
{
Son1 son;
son.m_A = 100; // 公有成员可以访问
// son.m_C = 200; // protected 继承过来也是 protected 权限,类外部无法访问
}
void test02()
{
Son2 son2;
// son2.m_A = 100; //保护权限,类外无法访问
// son2.m_C = 100; //保护权限,类外无法访问
}
void test03()
{
Son3 son3;
// son3.m_A = 100; // private 权限,类外无法访问
// son3.m_B = 100; // 父类隐私的成员子类无法访问
// son3.m_C = 100; // private 权限,类外无法访问
}
int main()
{
return 0;
}
父类的 private
成员,会被继承下去,只是被隐藏了;
- 父类中非静态成员,都会被继承下去
- 父类中的私有成员,只是被编译器隐藏了,因此只是访问不到;
通过 vs
提供的工具可以查看对象的内存模型
cl /d1 reportSingleClassLayoutSon "main.cpp"
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
int m_A = 30;
protected:
int m_B = 20;
private:
int m_C = 10;
};
//父类中非静态的所有成员,都会被继承下去
//父类中私有的成员,只是被编译器隐藏了,只是访问不到
class Child : public Base
{
public:
int m_D;
};
void test01()
{
Child child;
cout << sizeof(child) << endl; // 16, m_C 被继承下来了,只是被隐藏了,不可以直接访问;不过通过指针手段仍然可以访问 m_C 的值
}
int main()
{
test01();
return 0;
}
子类继承父类后,当创建子类对象,也会调用父类的构造函数;
问题:父类和子类的构造和析构顺序谁先谁后?
示例:
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
Base()
{
cout << "Base 构造函数" << endl;
}
~Base()
{
cout << "Base 析构函数" << endl;
}
};
class Son : public Base
{
public:
Son()
{
cout << "Son 构造函数" << endl;
}
~Son()
{
cout << "Son 析构函数" << endl;
}
};
void test01()
{
//构造顺序:先调用父类构造函数,再调用子类构造函数
//析构顺序:先调用子类析构函数,再调用父类析构函数
Son son;
}
int main()
{
test01();
return 0;
}
构造/析构函数调用顺序:(满足先进后出的原则)
- 先调用父类构造函数,再调用子类构造函数
- 先调用子类析构函数,再调用父类析构函数
当父类和子类中存在相同的成员时,如何通过子类对象,访问到子类对象,访问到子类或者父类中同名的数据呢?
- 访问子类同名成员,直接访问即可;
- 访问父类名称成员,需要加作用域;
示例:
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
int m_A = 20;
void func()
{
cout << "Base func " << endl;
}
};
class Son : public Base
{
public:
int m_A = 30;
void func()
{
cout << "Son func " << endl;
}
};
void test01()
{
Son son;
cout << "Son m_A = " << son.m_A << endl;
cout << "Base m_A = " << son.Base::m_A << endl;
cout << "---------------" << endl;
Son *son2 = new Son;
cout << "Son2 m_A = " << son2->m_A << endl;
cout << "Base2 m_A = " << son2->Base::m_A << endl;
delete son2;
}
void test02()
{
Son son;
son.func();
son.Base::func();
cout << "---------------" << endl;
Son *son2 = new Son;
son2->func();
son2->Base::func();
}
int main()
{
// test01();
test02();
return 0;
}
总结:
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员,加作用域可以访问到父类中同名函数
问题:继承中同名的静态成员在子类对象中如何进行访问?
静态成员和非静态成员出现同名,处理方式一致;子类直接通过 .
调用,调用父类需要加作用域
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
示例:
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base - static void func() " << endl;
}
};
class Son : public Base
{
public:
static int m_A;
static void func()
{
cout << "Son - static void func()" << endl;
}
};
int Base::m_A = 100;
int Son::m_A = 200;
// 同名静态成员属性
void test01()
{
// 1,通过对象访问
Son s;
cout << "Son m_A = " << s.m_A << endl; // 子类 200
cout << "Son Base m_A = " << s.::Base::m_A << endl; // 父类 100
// 2.通过类名访问
cout << Son::m_A << endl;
cout << Son::Base::m_A << endl;
}
// 同名静态函数
void test02()
{
// 1.通过对象访问
Son s;
s.func();
s.Base::func();
cout << "------------------" << endl;
// 2.通过类名访问
Son::func();
Son::Base::func();
}
int main()
{
// test01();
test02();
return 0;
}
总结:
同名的静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象调用和通过类名调用)