类与对象
类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。
将类的成员函数称为类的方法(Method)。
在面向对象的编程语言中,经常把函数(Function)称为方法(Method)。
类的定义
class
是 C++ 中新增的关键字,专门用来定义类
class 类名 { public: // 成员变量 // 成员函数 };
最后的;
很重要,注意检查,防止没必要的错误
public
也是 C++ 的新增关键字,它只能用在类的定义中,表示类的成员变量或成员函数具有“公开”的访问权限
创建对象
创建类:
class Student { public: string name; int age; void say() { cout << name << "说:我今年" << age << "岁了!" << endl; } };
创建对象
class 类名 对象名; 或 类名 对象名; 例如: class Student LiMing; Student LiMing;
在创建对象时,class 关键字可要可不要,Student是类名,LiMing是对象名
除了创建单个对象,还可以创建对象数组
Student AllStu[100]; // 这就创建类容量为100的对象数组
访问类的成员
创建对象以后,可以使用点号.
来访问成员变量和成员函数
// 使用成员变量 LiMing.name = "李明"; LiMing.age = 25; // 使用成员函数 LiMing.say();
使用对象指针
创建的对象 stu 在 栈上 分配内存,指针执行那个该对象,需要使用 取地址符 &
获取它的地址
Student stu; Student *pStu = &stu;
pStu 是一个指针,它指向 Student 类型的数据,也就是通过 Student 创建出来的对象。
需要使用 new
关键字
类名* 对象名 = new 类名; 例如: Student *pStu = new Student;
使用 new 在 堆上 创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数。
对象指针,可以通过箭头 ->
来访问对象的成员变量和成员函数
栈内存是程序自动管理的
堆内存由程序员管理,对象使用完毕后可以通过手动释放
new 和 delete 往往成对出现,以保证及时删除不再使用的对象,防止无用内存堆积。
Student* LiLei = new Student; LiLei->name = "李雷"; LiLei->age = 63; LiLei->say();
例子:
#include <iostream> #include <string> using namespace std; class Student { public: // 成员变量 string name; int age; // 成员函数 void say() { cout << name << "说:我今年" << age << "岁了!" << endl; } }; int main(int argc,char** argv) { // 创建实例对象 Student LiMing; LiMing.name = "李明"; LiMing.age = 25; LiMing.say(); // 使用指针创建实例对象 Student* LiLei = new Student; LiLei->name = "李雷"; LiLei->age = 63; LiLei->say(); delete LiLei;// 释放空间 return 0; }
匿名对象
格式:
(new 类名) -> 成员函数或成员变量; 例如: (new Student)->name = "小明"; (new Student)->age = 23;
每个匿名对象互不影响,互不关联,都是单独的个体
成员函数
类这种数据类型是一个包含成员变量和成员函数的集合。
成员函数和普通函数类似,都有返回值和参数列表,但作用域不同
成员函数可以的定义在类体内,也可以定义在类外
class Student { public: // 成员函数,在类体内部声明 void say() { cout << "Hello World!" << endl; } };
在类体外声明,需要用到 域解析符(也称作用域运算符或作用域限定符) ::
格式: class 类名 { 数据类型 函数名(参数列表); } (inline) 数据类型 类名::函数名(参数列表) { // 函数体 } 例如: class Student { public: void say(); }; void Student::say() { cout << "Hello World!" << endl; }
inline 是可选的,可根据具体情况而定
在类体外定义 内联 (inline
) 函数的方式,必须将类的定义和成员函数的定义都放在同一个头文件中(或者同一个源文件中),否则编译时无法嵌入到类中
类成员的权限访问
成员函数和成员变量的访问权限修饰符:
- public
- private
- protected
(private)私有成员变量和成员函数只能在类内部使用,在类外都是无效的
成员函数和成员变量无论使用何种修饰符修饰,在类内部,均可以随意使用
在类外部,只能通过声明的实例对象访问访问 public 属性的成员,不能访问 private、protected 属性的成员
class Student { // 私有化,只能在类内部使用 private: int age; // 保护模式,只能在类及 子类(继承) 中使用 protected: string name; // 公共的,可以任意访问 public: void say() { cout << name << "今年" << age << "岁!" << endl; } };
一般情况下,需要将类的成员变量隐藏(private),外部不能直接修改,需要使用特定的接口来set函数,使用 get函数来获取,(在英语中,可以知道 set-设置 ,get-获取, 语义化,更容易理解)
设置成员变量的函数的名字通常以get开头,后跟成员变量的名字,所以通常称为 get 函数。
读取成员变量的函数的名字通常以set开头,后跟成员变量的名字,所以通常称为 set 函数。
class Student { private: int age; public: void setAge(int age) { age = age; } int getAge() { return age; } };
在setAge中,存在两个age,会有些分不清,会产生混淆,因此在定义私有变量命名时,使用特殊格式,例: m_age
class Student { private: int m_age; public: void setAge(int age) { m_age = age; } int getAge() { return m_age; } };
如果不写任何修饰符,就会 默认为 private。
声明为 private 的成员和声明为 public 的成员的次序任意,既可以先出现 private 部分,也可以先出现 public 部分。
this指针
this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。
this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的。
this 是一个指针,要用->来访问成员变量或成员函数
class Student { private: int age; public: void setAge(int age) { this->age = age; } } 使用this,可以清晰的区分参数,有助于理解类的作用
this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。
this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时才给 this 赋值。
#include <iostream> #include <string> using namespace std; class Student { // 成员变量 private: int age; string name; public: // 成员函数 void setAge(int age) { // 设置age this->age = age; } void setName(string name) { // 设置name this->name = name; } int getAge() { // 获取age return this->age; } string getName() { // 获取name return this->name; } void say() { cout << this->name << "说:来,骗,来,偷袭," << this->age << "岁的老同志" << endl; } }; int main(int argc,char** argv) { Student Sir; Sir.setName("马保国"); Sir.setAge(69); Sir.say(); return 0; } 结果: 马保国说:来,骗,来,偷袭,69岁的老同志
构造函数
构造函数 : 名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),而是在创建对象时自动执行
构造函数 :在创建对象的同时为成员变量赋值
默认情况下,系统会默认给类添加一个构造函数
Student(){}
当已存在构造函数,系统就不会添加默认的构造函数
不管是声明还是定义,构造函数名前面都不能出现返回值类型,即使是 void 也不允许;
#include <iostream> #include <string> using namespace std; class Student { private: // 成员变量 int age; string name; public: // 构造函数 Student(int age,string name){ this->age = age; this->name = name; } // 成员函数 void say() { cout << this->name << "说:来,骗,来,偷袭," << this->age << "岁的老同志" << endl; } }; int main(int argc, char** argv) { Student Sir(69,"马保国"); Sir.say(); return 0; } 结果: 马保国说:来,骗,来,偷袭,69岁的老同志
一个类可以存在多个构造函数
构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的,防止出现错误,一般可以手动添加一个 无参 构造函数
创建对象时只有一个构造函数会被调用
#include <iostream> #include <string> using namespace std; class Student { private: int age; string name; public: // 无参构造函数 Student() { this->age = 69; this->name = "老同志"; } // 构造函数 Student(int age,string name){ this->age = age; this->name = name; } // 成员函数 void say() { cout << this->name << "说:来,骗,来,偷袭," << this->age << "岁的老同志" << endl; } }; int main() { Student Sir; Sir.say(); return 0; } 结果: 老同志说:来,骗,来,偷袭,69岁的老同志
也可以使用 对象指针
Student* Sir = new Student(); Sir->say(); delete Sir; Student* Man = new Student(69,"马保国"); Man->say(); delete Man;
构造函数也可以定义在 类体 外
class Student { private: int age; string name; public: Student(int age, string name); }; Student::Student(int age, string name) { this->age = age; this->name = name; }
构造函数初始化列表
格式 ( Student 类):
class Student { private: int m_age; string m_name; public: Student(int age, string name); }; Student::Student(int age, string name):m_age(age),m_name(name) {}
定义构造函数时并没有在函数体中对成员变量一一赋值,其函数体为空(可以有其他语句)
而是在函数首部与函数体之间添加了一个冒号 :
,后面紧跟 m_age(age),m_name(name)
, 语句,这个语句的意思相当于函数体内部的 m_age = age;m_name = name;
语句
使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便
成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。
构造函数初始化列表还有一个很重要的作用 : 初始化 const 成员变量
class Student { private: const int m_len = 0; public: Student(int len); void say() { cout << this->m_len << endl; } }; Student::Student(int len): m_len(len){}
析构函数
创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是 析构函数。
析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。
- 构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个
~
符号。
- 析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。
- 如果没有定义,编译器会自动生成一个默认的析构函数。
例如: ~Student(){}
用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数。
class Student { private: int m_len; int* arr; public: Student(int len); ~Student(); }; Student::Student(int len):m_len(len){ // 在堆上创建数组 if( len > 0) this->arr = new int[len]; else this->arr = new int[10]; } Student::~Student() { delete arr; // 释放数组内存 }
new 创建的 对象 位于 堆区 ,通过 delete 删除时才会调用析构函数;如果没有 delete,析构函数就不会被执行。
#include <iostream> using namespace std; class Student { private: int m_len; public: Student(int len); ~Student(); }; Student::Student(int len):m_len(len){} Student::~Student() { cout << this->m_len << endl; } int main(int argc, char** argv) { Student* Man = new Student(1); Student* Woman = new Student(2); delete Man; return 0; } 输出结果为: 1
静态成员
静态成员变量
静态成员变量是一种特殊的成员变量,它被关键字 static
修饰
例如: class Student { public: static int count; };
static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,只会 分配一份内存,所有对象使用的都是这份内存中的数据。
静态成员变量,不能通过构造函数初始化,因为其不是类的非静态成员
静态成员变量初始化,需要在类外完成
访问静态成员变量的方式:
//通过类类访问 static 成员变量 Student::count = 10; //通过对象来访问 static 成员变量 Student stu; stu.count = 20; //通过对象指针来访问 static 成员变量 Student *pstu = new Student; pstu -> count = 20;
static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问。
在类内部可以访问静态成员变量,也可以使用this指针
#include <iostream> #include <string> using namespace std; class Student { private: string m_name; public: static int count; public: Student(string name):m_name(name){ count++; }; void say() { // 类内部访问 静态成员变量 cout << "第 " << this->count << " 位同学: " << this->m_name << endl; } }; // 初始化静态成员变量 int Student::count = 0; int main() { Student s1("小明"); cout << s1.count << endl; s1.say(); Student* s2 = new Student("李雷"); cout << s2->count << endl; s2->say(); delete s2; cout << "全班有" << Student::count << "位同学" << endl; return 0; } 结果: 1 第 1 位同学: 小明 2 第 2 位同学: 李雷 全班有2位同学
- 一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量
- static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。
static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。
- 静态成员变量必须初始化,而且只能在类体外进行
- 静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。当通过对象名访问时,对于不同的对象,访问的是同一份内存。
静态成员函数
静态成员函数只能访问静态成员
静态成员函数没有 this 指针,函数内部不能使用this指针
class Student { public: static int count; public: static void say() { cout << count << endl; // cout << this->count << endl; 错误的格式 } void run() { this->say(); // 可以访问静态成员函数 } };
静态成员函数在声明时要加 static,在定义时不能加 static
class Student { public: static int count; public: static void say(); void run(); }; void Student::say() { cout << count << endl; } void Student::run() { this->say(); }
const成员
const 成员变量,只需在定义时,使用 const
关键字,通过 构造函数 进行初始化
const 成员函数 :称为常成员函数。
const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值
- 常成员函数 需要在声明和定义的时候在函数头部的 结尾 加上 const 关键字
- 函数 开头 的 const 用来修饰 函数的返回值
- 必须在成员函数的声明和定义处同时加上 const 关键字
int getScore() const; int Student::getScore() const { return m_score; }
- 通常将 get 函数设置为常成员函数
#include <iostream> #include <string> using namespace std; class Student { private: string m_name; int m_score; public: // 构造函数 Student(string name,int m_score); // 类体内声明 string getName() const { // this->m_age = "小明"; 显示错误,不能使用this指针 return m_name; }; // 类体外声明 int getScore() const; }; int Student::getScore() const { return m_score; } // 构造函数 Student::Student(string name,int score) : m_name(name),m_score(score) {}; int main() { Student s("李雷",75); cout << s.getName() << endl; cout << s.getScore() << endl; return 0; }
const 对象
const用来修饰对象,称为常对象
一旦将对象定义为 常对象 之后,就 只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)
const 类名 对象名; 例如: const Student Sir;
可以定义 const 指针
const Student* Sir = new Student;
例如:
#include <iostream> #include <string> using namespace std; class Student { private: string m_name; int m_score; public: // 构造函数 Student(string name, int score) : m_name(name), m_score(score) {}; int getScore() const { return m_score; } void show() { cout << m_name << "的英语" << m_score << endl; } string getName() const { //show(); 错误,不能使用this指针,也不能访问 cout << "英语" << getScore() << "的"; return m_name; }; }; int main() { const Student s("李雷", 75); cout << s.getName() << endl; //s.show(); 错误 const Student * sir = new Student("小强",100); cout << sir->getName() << endl; sir->show(); 错误 return 0; }
友元函数
借助友元(friend),可以使得 其他类 中的 成员函数 以及全局范围内的函数 访问 当前类的 private 成员。
使用关键字 friend
, 借助 友元 可以访问与其有好友关系的类中的私有成员
注意,友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象。
参数 传递对象(可以直接传递对象,也可以传递对象指针或对象引用),并在访问成员时指明对象。
将非成员函数声明为友元函数
注意:
- 友元函数的定义,一定要在类的定义之后,否则无法访问
#include <iostream> #include <string> using namespace std; class Student { private: string m_name; string m_work; public: // 构造函数 Student(string name, string work) :m_name(name), m_work(work) {}; public: // 友元函数 friend void show(Student* s); friend void say(Student s); }; void show(Student* s) { cout << "朋友们好啊, 我是" << s->m_work << s->m_name << endl; } void say(Student s) { cout << "大家好, 我是" << s.m_work << s.m_name << endl; } int main() { Student mbg("马保国","浑元形意太极门掌门人"); show(&mbg); Student cxk("坤坤","练习两年半的偶像练习生"); say(cxk); return 0; } 结果: 朋友们好啊, 我是浑元形意太极门掌门人马保国 大家好, 我是练习两年半的偶像练习生坤坤
将其他类的成员函数声明为友元函数
friend类 表示 拥有友元函数(friend)的类
member类 表示 拥有成员函数的类
类之间的友元函数,需要注意类的顺序,以及成员函数定义的位置和方式:
- member类 要先定义,之后再定义 friend类
下例: 由于 类 Introduct
中的成员函数要 作为 类 Student
的友元函数,因此可以访问类 Student
的成员 ,所以 类 Introduct
需要先定义
- 类必须在正式声明之后才能使用;但是某些情况下,只要做好提前声明,也可以先使用。由于 friend类,在 member类 之后定义,当执行到 member类 时,需要用到friend类 ,但是 friend类 没有定义, 因此需要 提前声明
下例:
class Student; // 很有必要的提前声明
- member类 的成员函数
声明
和实现
要分开,需要将实现
方式放在 friend类 的定义之后
- friend类 对应的就是 出现
friend
的类,共享自己的数据
定义的一般顺序: 第一步: class friend; 第二步: class member{ // 成员变量 , 成员函数 // 需要 友元的成员函数 声明 } 第三步: class friend{ // 成员变量 , 成员函数 // friend member类的 友元成员函数 } 第四步: member类的 友元成员函数 的实现
例子:
#include <iostream> #include <string> using namespace std; class Student; // 很有必要的提前声明 // 第一个类, member类 class Introduct { private: static int count; public: // 构造函数 Introduct() { count++; }; public: // 成员函数 void show(Student* s); void say(Student s); }; // 第二个类, friend类 class Student { private: string m_name; string m_work; public: // 构造函数 Student(string name, string work) :m_name(name), m_work(work) {}; public: // 友元函数 friend void Introduct::show(Student* s); friend void Introduct::say(Student s); }; // 第一个类的成员函数,即第二个类的友元函数 void Introduct::show(Student* s) { cout << this->count << ". 朋友们好啊, 我是" << s->m_work << s->m_name << endl; } void Introduct::say(Student s) { cout << this->count << ". 大家好, 我是" << s.m_work << s.m_name << endl; } int Introduct::count = 0; int main() { Student mbg("马保国","浑元形意太极门掌门人"); (new Introduct)->show(&mbg); // 匿名对象 Student cxk("坤坤","练习两年半的偶像练习生"); (new Introduct)-> say(cxk); // 匿名对象 } 结果: 1.朋友们好啊, 我是浑元形意太极门掌门人马保国 2.大家好, 我是练习两年半的偶像练习生坤坤
友元类
将类 B 声明为类 A 的友元类,那么类 B 中的所有成员函数都是类 A 的友元函数,可以访问类 A 的所有成员,包括 public、protected、private 属性的。
除非有必要,一般不建议把整个类声明为友元类,而只将某些成员函数声明为友元函数
friend类 表示 拥有友元函数(friend)的类
member类 表示 拥有成员函数的类
注意:
- 优先定义 friend类,其次是 member类
- friend类 对应的就是 出现
friend
的类,共享自己的数据
#include <iostream> #include <string> using namespace std; // 第一个类, friend类 class Introduct { private: static int count; public: // 构造函数 Introduct() { count++; }; public: // 友元类 friend class Student; }; // 第二个类, member类 class Student { private: string m_name; string m_work; public: // 构造函数 Student(string name, string work) :m_name(name), m_work(work) {}; public: // 友元函数 void show(Introduct* s); // 类体外 void say(Introduct s) // 类体内 { cout << s.count << ". 大家好, 我是" << this->m_work << this->m_name << endl; }; }; void Student::show(Introduct* s) { cout << s->count << ". 朋友们好啊, 我是" << this->m_work << this->m_name << endl; } // 静态成员变量 int Introduct::count = 0; int main() { Student mbg("马保国","浑元形意太极门掌门人"); Introduct mg; mbg.show(&mg); Student cxk("坤坤","练习两年半的偶像练习生"); Introduct kk; cxk.say(kk); return 0; } 结果: 1.朋友们好啊, 我是浑元形意太极门掌门人马保国 2.大家好, 我是练习两年半的偶像练习生坤坤
友元的关系不能传递。如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类, 类C 不能使用 类 A 的成员
友元的关系是单向的而不是双向的。如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员。
class与struct
struct 是 C语言 的关键字
在C语言中,struct 只能包含成员变量,不能包含成员函数。而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。
struct 和 class 基本是通用的,仍存在一些区别:
- 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
- class 继承默认是 private 继承,而 struct 继承默认是 public 继承
- class 可以使用模板,而 struct 不能
例如:
#include <iostream> #include <string> using namespace std; // 使用 "struct" 结构体 struct Student { int number = 6; static int count; private: // 成员变量 string m_name; string m_work; public: // 构造函数 Student(string name, string work) :m_name(name), m_work(work) { // 计数 count++; }; // 析构函数 ~Student() { cout << this->m_name <<endl; }; public: // 成员函数 void show(); // 类体外 void say() // 类体内 { cout << this->count << ". 大家好, 我是" << this->m_work << this->m_name << endl; } }; void Student::show() { cout << this->count << ". 朋友们好啊, 我是" << this->m_work << this->m_name << endl; } // 静态成员变量初始化 int Student::count = 0; int main() { Student mbg("马保国","浑元形意太极门掌门人"); mbg.show(); Student cxk("坤坤","练习两年半的偶像练习生"); cxk.say(); // 默认为 public ,因此可以访问number cout << cxk.number << endl; return 0; } 结果: 1. 朋友们好啊, 我是浑元形意太极门掌门人马保国 2. 大家好, 我是练习两年半的偶像练习生坤坤 6 坤坤 马保国
简书同步: https://www.jianshu.com/p/bc5ae023f442
CSDN同步:https://blog.csdn.net/qq_45127998/article/details/109649476