类与对象
什么是类?
描述有共同特征的事务的概念
作用:代码中 创建对象
什么是对象?
生活中: 就是指真实存在的事物。
代码中: 模拟真实的事物,使用类创建得到。
类与对象的关系
生活中:
先有 对象 后有 类
代码中:
现有 类 后有 对象
一个类 可以创建多个对象
多个对象可以属于 同一个类
多个对象之间 互不干扰
如何定义一个类
class 类名 { 成员变量:【定义在类中的变量】 描述事物的静态特征 如人的 身高、肤色、身高、 一个类可以有无数个成员变量 也可以没有 构造函数:创建对象 析构函数:销毁回收对象 拷贝构造:对象赋值给对象 成员函数: 描述事物的动态特征 如吃饭、睡觉、喝水、走路.... }
示例:
定义一个人类
成员变量: 姓名,年龄,身高
成员函数: 吃饭 睡觉
#include <iostream> using namespace std; class Person { char name[50]; // 避免使用指针 发生浅拷贝 和结构体一样 int age; int height; void eat(char *foodName) { cout << name << "吃" << foodName << endl; } void sleep(); // 声明和定义可以分开 }; void Person::sleep() { cout << name << "睡觉" << endl; } int main(int argc, char const *argv[]) { return 0; }
注意:
因为类的定义只需定义一次,所以我们一般将其写在头文件中 但是类中有成员函数,头文件中对函数只声明不实现 所以在头文件中定义类,
类定义以及创建对象 的标准格式:
定义和声明分开 反正后面都这样写
头文件
#ifndef xxz // 判断是否存在宏 #define xxz // 定义宏 using namespace std; #include <iostream> class Person { public: // 修饰符 char name[50]; // 避免使用指针char *name 发生浅拷贝 和结构体一样 int age; int height; void eat(char *foodName); void sleep(); // 声明和定义可以分开 }; #endif
源文件
#include "01.h" // :: 作用域修饰符 void Person::eat(char *foodName) { cout << name << "吃" << foodName << endl; } void Person::sleep() { cout << name << "睡觉" << endl; }
使用文件
#include <iostream> #include "01.h" #include <string.h> int main(int argc, char const *argv[]) { // 创建对象 Person p1; p1.age = 18; p1.height = 180; strcpy(p1.name, "小农民"); cout << "p1.name " << p1.name << endl; p1.eat("米饭"); return 0; }
访问权限修饰符
类中成员 不加修饰符 默认使用 私有的 private 修饰
但是 后面创建类的时候
推荐
成员变量 使用 private 修饰
成员函数 使用 public 修饰
直接操作值 就比如 你定义 age 别人直接使用 给赋值 负数 那就会出错
所以 将成员变量全写私有 函数写公共 通过函数 get set 成员变量 来限定各个成员变量 。
作用: 限定类中成员的访问范围
public: 程序任何一处 都可直接使用 protected: 当前类中或子类中使用 private: 当前类中使用
使用:
class 类名
{
private:
成员
protected:
成员
public:
成员
}
类的设计(封装性)
- 1,私有化其成员变量
- 2,公共其成员函数
- 3,提供对成员变量操作的get与set函数
class Person
{
private:
int age = 0;
int height = 0;
char *name = NULL;
public:
void eat()
{
cout << name << "吃饭" << endl;
}
void sleep();
void setAge(int a)
{
// this:那个对象调用该函数this就是该对象的指针变量
// this->age = age;
if (a < 0)
{
this关键字
概念
作用
age = 0;
return;
}
age = a;
}
int getAge()
{
return age;
}
void setHeight(int h)
{
height = h;
}
int getHeight()
{
return height;
}
void setName(char *n)
{
if (name == NULL)
{
return;
}
strcpy(name, n);
}
};
// 虽然函数的声明与实现分离
// 实现在类外,但是依据可以直接使用priate修饰的成员
void Person::sleep()
{
cout << name << "睡觉" << endl;
}
this关键字
不写它也有 是忽略显示了 而不是没有
概念:
谁调用this所在的函数,this就代表谁,是个指针
作用:
1,在当前类中调用当前类的成员,此时this可以忽略不写
2,当局部变量与成员变量重名
变量名 局部变量
this->变量名 成员变量
1 构造函数
注意:
- 类中没有自定义 构造函数 系统将提供一个无参构造
- 要有自定义构造函数,系统就不会为其提供无参构造
- 一个类中可以有多个构造函数,这多个构造函数的关系是重载
作用: 创建本类对象时调用
语法:
类名 (形参列表) { 函数体; }
调用:
调用无参构造: 类名 对象名; 调用有参构造: 类名 对象名(实参列表);
示例:
#include <iostream> #include <string.h> using namespace std; class Person { private: int age; char name[50]; public: // 无参构造 Person() { cout << "无参构造被调用" << endl; } // 有构造函数 没有 返回值类型 所以一定没有返回值 Person(int age, char *name) { this->age = age; strcpy(this->name, name); // 用 深拷贝 cout << "有参被调用" << endl; } }; int main(int argc, char const *argv[]) { // 此时并没有书写构造函数 但还是可以创建类对象 Person p1; // 默认调用的就是无参构造 cout << "==============" << endl; Person p2(18, "张十一"); return 0; }
2 析构函数
析构函数 没有参数
且一个 类 只能写 一个析构函数
如果不写系统默认提供一个默认的析构函数
作用: 当对象销毁时调用
语法:
~类名() { 函数体; }
示例:
#include <iostream> #include <stdlib.h> #include <string.h> using namespace std; // 析构函数 class Person { private: char *name; public: Person() { cout << "person 的无参构造" << endl; name = (char *)calloc(50, sizeof(char)); } Person(char *name) { cout << "person 的有参构造" << endl; this->name = (char *)calloc(50, sizeof(char)); strcpy(this->name, name); } // 析构函数没有参数 销毁释放内存 ~Person() { cout << name << "\t" << "销毁了~~~" << endl; free(name); } }; int main(int argc, char const *argv[]) { // 此时属于局部变量 // 生命周期: // 随着 函数调用而生成 随着函数的执行结束而销毁 Person p1; Person p("张三"); return 0; }
3 拷贝构造
而系统提供的就是 浅拷贝的方式 所以 一般都自己写
浅拷贝:
假设 指针A 指向了 堆区的 内存 再后面 有指针B 将指针A 的值赋值给指针B 所以 指针A 和 指针B 将指向同一块内存
假设指针A和B分别是对象A和B的两个成员,这样会产生两个问题:
问题一:
不管是 对象A 还是 对象B 只要其中一个对象 修改了内存中的数据 另一个对象 看到的数据 也会改变。
问题二:
一般会将释放内存的代码存放在析构函数中,但要是如果对象A 和 对象B其中有一个销毁了,调用了析构函数
其中一个对象释放了内存,另一个对象的指针就成了野指针。
深拷贝:
概念:
如果有指针A 指向一块内存,那么就重新分配一块相同大小的内存,让指针B指向新内存。
然后再把指针A 指向内存中的数据 拷贝到新内存中,
这样的拷贝方式很彻底,拷贝之后,大家各自操作自己的指针和内存。就不会有任何冲突
深拷贝步骤:
-
1 分配内存
-
2 拷贝数据
是为了防止 浅拷贝
就是传引用 &p 别传本身 p 会多占内存
作用:
这个类的对象A 赋值给这个类的对象B 时会触发拷贝构造 类名 对象名A; 类名 对象名B = 对象名A;
语法:
类名(const 类型 &别名) { 函数体; }
注意:
此时不会触发构造函数 注意浅拷贝, 此时一定要进行深拷贝,避免重复释放导致的崩溃 !!!!
初始化列表
语法:
# 类名(形成列表):成员变量名(形参中的变量名),成员变量名(形参中的变量名),.....
{
函数体;
}
class A
{
private:
int x;
int y;
int z;
public:
A()
{
}
A(int x, int y, int z) : x(x), y(y), z(z)
{
}
} A a1(1, 11, 111);
A a2(2, 22, 222);
注意:
基本类型除外实现的是浅拷贝
隐式转换
当调用的构造函数 只有一个参数的时候可以进行隐式转换 多参就不能使用
class A
{
private:
int x;
int y;
int z;
public:
A()
{
}
A(int x) : x(x)
{
}
}
A a1 = 10;
A a2 = 20;
语法:
# 类名 对象名 = 实参;
explicit 关键字
作用: 禁止隐式 转换
语法:
explicit 类名(形参) { }
new与delete
new : 在堆内存中 创建对象
delete : 释放对象 堆内存
new
语法:
类名 *对象指针 = new 类名; // 调无参
类名 *对象指针 = new 类名(实参列表); //调有参
步骤:
> 先在堆中开辟内存
> 再调用构造函数
delete
作用:释放对象 堆内存
语法:
delete 对象指针;
步骤:
>先 执行 析构函数
> 再释放对象指针指向内存
和 new 相反
类 B的对象作为类 A的成员变量
创建时构造函数的调用顺序
先成员构造
在自己构造
销毁时析构函数的调用顺序
先自己析构
在成员析构
注意:
使用new创建A类对象时,依然会先创建其成员类B的对象
先成员构造
后自己构造
使用new是在堆中开辟的,无法自动释放,需要使用delete,此时
先自己析构
后成员析构
class B{
public:
char *name = (char *)calloc(50,1);
~B()
{
free(name);
}
}
class A{
B b;
}
int main()
{
A *a = new A;
delete a;
}
对象数组
作用:
存储对象的数组
静态创建
动态创建:
【 是在堆区创建的 】
类名 *数组名 = new 类名[]{对象1,对象2,对象3,...};
注意:
释放语法:
delete 数组名; // 只释放了一个
delete [] 数组名; //释放所有
静态成员
概念:
使用 static 修饰的 成员为 静态成员
修饰成员变量的特点
1,使用static修饰的成员变量属于该类,给类的所有对象共同持有一份 2,不占用对象的内存空间,该成员变量存储在静态全局区 3,可以使用类名直接调用 类名::成员变量名 类名::成员变量名 = 值; 注意: 修饰的成员变量需要类外初始化,其语法为 数据类型 类名::变量名 = 值; 如果不初始化会报该成员变量未定义
修饰成员函数的特点:
1, 可以通过类名直接调用,也可以使用该类对象调用 注意: 2. static修饰的成员函数中只能使用该类的静态成员,static修饰的成员函数中不能使用this关键字
为什么不能使用this关键字 :
因为静态成员函数可以使用类名直接调用,此时this没有可代表的对象
const修饰的成员函数
语法:
返回值类型 函数名(形参列表) const
{
}
// 特点: 使用这个函数的时候 只能读值 不能修改成员值
示例:
#include <iostream>
using namespace std;
class Dog
{
private:
int age;
public:
//有参构造
Dog(int age)
{
this->age = age;
cout<<"有参构造调用!"<<endl;
}
//拷贝构造
Dog(const Dog &dog)
{
cout<<"拷贝构造调用!"<<endl;
this->age = age;
}
// void show()const;
void show();
};
// void Dog::show()const
void Dog::show()
{
age=101; // 这样就可以改值
cout<<"狗狗 "<< age<<"岁"<<endl;
}
int main(int argc, char const *argv[])
{
Dog d1(12); //类对象创建
d1.show(); //对象调用函数
return 0;
}
链式编程
#include <iostream> using namespace std; class A { public: A *test01() { cout << "test 01" << endl; return this; } A *test02() { cout << "test 02" << endl; return this; } A *test03() { cout << "test 03" << endl; return this; } A *test04() { cout << "test 04" << endl; return this; } }; int main(int argc, char const *argv[]) { A a; // a.test01(); // a.test02(); // a.test03(); // a.test04(); a.test01()->test02()->test03()->test04()->test01(); return 0; }