1.简述一下什么是面向对象
面向对象是一种编程思想,把一切东西看成一个个对象,比如人、手机、电脑、鼠标等,他们个自都有属性,比如手机的品牌,型号,颜色等,把这些对象拥有的属性变量和操作这些属性变量的函数打包成一个类来表示,类就是对同一类对象的抽象总结,是一种概念
一个类主要包括:
属性:
用来描述对象的数据元素,通常是一个名词变量,例如身高、体重、价格等,也称为“成员变量”或“数据变量”
行为:
用来描述对执行的具体操做,通常对属性进行操作
成员变量和成员函数统称为“成员”
类的函数定义参考代码,以手机为例
using namespace std;
class MobilePhone // 帕斯卡命名法:所有单词的首字母大写
{
public: // 公有权限:最开放的一种权限
string brand; // 品牌
string model; // 型号luanqidng
int weight; // 重量
void run_game() // 运行游戏
{
cout << "timi" << endl;
}
void play_music()
{
cout << "只因你太美" << endl;
}
void communicate()
{
cout << "你好" << endl;
}
};
int main()
{
return 0;
}
1.1面向对象和面向过程的区别
面向过程:根据业务逻辑从上到下写代码
面向对象:将数据与函数绑定到一起,进行封装,这样能更快的开发程序,减少重复代码的重写过程。
2.实例化对象
类是一个抽象的概念,因此需要按照这个概念创建对应的对象实体,C++中有两种类型的对象:
栈内存对象:
1.在生命周期结束(所在的花括号执行完)后,自动被销毁。
2.栈内存对象用“.”调用成员
堆内存对象:
1.需要使用new关键字创建,使用delete关键字销毁如果不销毁,则会持续存在,容易导致内存泄漏的问题,内存泄漏最终可能会导致程序卡顿,甚至卡死。
2.堆内存对象通常使用指针来保存堆内存对象的地址。
3.堆内存对象使用 -> 调用成员。
using namespace std;
class MobilePhone // 帕斯卡命名法:所有单词的首字母大写
{
public: // 公有权限:最开放的一种权限
string brand; // 品牌
string model; // 型号
int weight; // 重量
void run_game() // 运行游戏
{
cout << "timi" << endl;
}
void play_music()
{
cout << "只因你太美" << endl;
}
void communicate()
{
cout << "你好" << endl;
}
};
int main()
{
// 栈内存对象
MobilePhone mp1;
// 调用属性
mp1.brand = "小米";
mp1.model = "13 Pro";
mp1.weight = 216;
cout << mp1.brand << " " << mp1.model << " "
<< mp1.weight << endl;
// 调用成员函数
mp1.communicate();
mp1.play_music();
mp1.run_game();
// 堆内存对象
MobilePhone* mp2 = new MobilePhone;
// 调用属性
mp2->brand = "红米";
mp2->model = "K60 Pro";
mp2->weight = 206;
cout << mp2->brand << " " << mp2->model << " " <<
mp2->weight << endl;
// 调用成员函数
mp2->communicate();
mp2->play_music();
mp2->run_game();
// 手动销毁mp2
delete mp2;
// 有时销毁后还能使用部分功能,但是不要这么做
mp2 = NULL; // 可以赋值为NULL防止delete后调用
return 0;
}
// 主函数执行完,mp1销毁
3.封装
将类的一些属性和细节隐藏,并重新公开给外部调用的接口。封装的写法并不唯一,在实际的过程中会结合业务需求而定,下面的以一个最基础的案例进行编写。通常先对属性私有化,使属性隐藏,然后根据当前属性的需求,通过getter函数和setter函数对类外分别公开读和写的功能。
4.构造函数
4.1 概念
1.是一种类内特殊函数,用来创建一个对象
2.如果程序员不手动编写构造函数,编译器会自动添加一个无参构造函数,且此时函数的函数体为空
3.如果程序员手写了一个构造函数,编译器不会再自动添加
4.构造函数要求与函数名必须与类名完全一致,而且构造函数无需写返回值
using namespace std;
class MobilePhone
{
private:
string brand;
string model;
int weight;
public:
// MobilePhone(){} // 编译器自动添加的无参构造函数
MobilePhone() // 手写构造函数
{
// 可以设定属性的默认值
brand = "山寨";
model = "8848钛金手机";
weight = 188;
cout << "生产了一部手机" << endl;
}
string get_brand()
{
return brand;
}
void set_brand(string b)
{
brand = b;
}
void set_model(string m)
{
model = m;
}
int get_weight()
{
return weight;
}
};
int main()
{
MobilePhone mp1; // 调用了无参构造函数
mp1.set_brand("小米");
mp1.set_model("13 Pro");
cout << mp1.get_brand() << " " << mp1.get_weight() << endl;
MobilePhone* mp2 = new MobilePhone; // 调用了无参构造函数
mp2->set_brand("红米");
mp2->set_model("K60 Pro");
cout << mp2->get_brand() << " " << mp2->get_weight() << endl;
delete mp2;
return 0;
}
4.2 传参
可以构造函数增加函数参数,使用参数给属性赋予初始值,使对象创建更灵活
#include <iostream>
using namespace std;
class MobilePhone
{
private:
string brand;
string model;
int weight;
public:
MobilePhone(string b,string m,int w)
{
brand = b;
model = m;
weight = w;
}
string get_brand()
{
return brand;
}
void set_brand(string b)
{
brand = b;
}
void set_model(string m)
{
model = m;
}
int get_weight()
{
return weight;
}
};
int main()
{
MobilePhone mp1("小米","13Pro",190); // 调用了三参构造函数
cout << mp1.get_brand() << " " << mp1.get_weight() << endl;
MobilePhone* mp2 = new MobilePhone("红米","K60Pro",199); // 调用了三参构造函数
cout << mp2->get_brand() << " " << mp2->get_weight() << endl;
delete mp2;
return 0;
}
4.3重载
构造函数支持函数重载,遵守之前函数重载的规则
using namespace std;
class MobilePhone
{
private:
string brand;
string model;
int weight;
public:
MobilePhone() // 重载
{
brand = "8848";
model = "钛金手机";
weight = 199;
}
MobilePhone(string b,string m,int w) // 重载
{
brand = b;
model = m;
weight = w;
}
string get_brand()
{
return brand;
}
void set_brand(string b)
{
brand = b;
}
void set_model(string m)
{
model = m;
}
int get_weight()
{
return weight;
}
};
int main()
{
MobilePhone mp1("小米","13Pro",190); // 调用了三参构造函数
MobilePhone* mp2 = new MobilePhone("红米","K60Pro",199); // 调用了三参构造函数
delete mp2;
MobilePhone mp3; // 调用无参构造
MobilePhone* mp4 = new MobilePhone; // 调用无参构造
delete mp4;
return 0;
}
4.4参数默认值
构造函数支持参数默认值设定
using namespace std;
class MobilePhone
{
private:
string brand;
string model;
int weight;
public:
MobilePhone(string b = "8848",
string m="钛金手机",
int w = 199)
{
brand = b;
model = m;
weight = w;
}
string get_brand()
{
return brand;
}
void set_brand(string b)
{
brand = b;
}
void set_model(string m)
{
model = m;
}
int get_weight()
{
return weight;
}
};
int main()
{
MobilePhone mp1("小米","13Pro",190); // 调用了三参构造函数
MobilePhone* mp2 = new MobilePhone("红米","K60Pro",199); // 调用了三参构造函数
delete mp2;
MobilePhone mp3; // 调用无参构造
MobilePhone* mp4 = new MobilePhone; // 调用无参构造
delete mp4;
return 0;
}
4.5构建初始化列表
构造初始化列表是一种简便的写法,可以用于给属性赋予初始值
using namespace std;
class MobilePhone
{
private:
string brand;
string model;
int weight;
public:
MobilePhone(string b = "8848",
string m="钛金手机",
int w = 199):brand(b),model(m),weight(w)
{}
void show() // 展示所有属性值
{
cout << brand << " " << model << " "
<< weight << endl;
}
};
int main()
{
MobilePhone mp1("小米","13Pro",190);
mp1.show();
return 0;
}
4.6拷贝构造函数
1.如果程序员在一个类中不手动编写拷贝构造函数,编译器会为这个类自动添加一个拷贝构造函数,拷贝构造函数与普通构造函数也是函数重载的关系。
2.对象和对象之间是独立存在的实体,数据也是相互独立的,拷贝构造函数可以把一个对象的属性值拷贝到新创建的对象中。
using namespace std;
class MobilePhone
{
private:
string brand;
string model;
int weight;
public:
MobilePhone(string b,string m,int w)
:brand(b),model(m),weight(w)
{}
// 编译器自动添加的拷贝构造函数
// MobilePhone(const MobilePhone& mp)
// {
// brand = mp.brand;
// model = mp.model;
// weight = mp.weight;
// }
void show()
{
cout << brand << " " << model << " "
<< weight << endl;
cout << &brand << " " << &model << " "
<< &weight << endl;
}
};
int main()
{
MobilePhone mp1("小米","13Pro",190);
mp1.show();
// 拷贝构造函数
MobilePhone mp2(mp1);
mp2.show();
return 0;
}
4.7拷贝构造函数可能会出现的问题
如果成员变量出现指针,在拷贝的过程中,会导致两个对象的成员变量指针保存同一份地址,指向同一个内存区域,不符合面向对象的特性。
4.8深拷贝与浅拷贝
当类中的成员变量出现指针,默认的浅拷贝,参考代码:
#include <iostream>
#include <string.h>
using namespace std;
class Dog
{
private:
char* name; // 特例
public:
Dog(char* n)
{
name = n;
}
void show_name()
{
cout << name << endl;
}
};
int main()
{
char c[20] = "wangcai";
Dog d1(c); // d1.name → c
Dog d2(d1); // d2.name → c
strcpy(c,"tiedan");
d1.show_name(); // tiedan
d2.show_name(); // tiedan
return 0;
}
此时需要手写构造函数,为每个对象的name属性单独开辟一个内存区域,且当前对象独享这个区域。
#include <iostream>
#include <string.h>
using namespace std;
class Dog
{
private:
char* name; // 特例
public:
Dog(char* n)
{
name = new char[20];
// 只拷贝内容
strcpy(name,n);
}
Dog(const Dog& d)
{
name = new char[20];
// 只拷贝内容
strcpy(name,d.name);
}
void show_name()
{
cout << name << endl;
}
};
int main()
{
char c[20] = "wangcai";
Dog d1(c); // d1.name → 堆区1
Dog d2(d1); // d2.name → 堆区2
strcpy(c,"tiedan");
d1.show_name(); // wangcai
d2.show_name(); // wangcai
return 0;
}
其实在实际开发中,也可以采用屏蔽拷贝构造函数这种简单粗暴的方式解决浅拷贝问题。屏蔽某个构造函数只需要把这个构造函数的代码从public区域移动到private区域。
5.析构函数
析构函数是与构造函数对立的函数。
构造函数 | 析构函数 |
手动调用 | 在对象被销毁时自动调用 |
通常用于在对象创建时初始化 | 通常用于在对象销毁时回收资源 |
可以被重载 | 不可以重载,因为没有参数 |
函数名是类名 | 函数名是~类名 |
#include <iostream>
using namespace std;
class Cat
{
public:
~Cat()
{
cout << "猫挂了" << endl;
}
};
int main()
{
cout << "主函数开始" << endl;
// Cat c1;
Cat* c2 = new Cat;
delete c2;
cout << "主函数结束" << endl;
return 0;
}
之前的深拷贝代码中,两个构造函数中都new出了name属性对应的堆内存空间,但是并没有回收,因此需要给Dog类增加析构函数,在析构函数中回收name占用的堆内存空间。
#include <iostream>
#include <string.h>
using namespace std;
class Dog
{
private:
char* name; // 特例
public:
Dog(char* n)
{
name = new char[20];
// 只拷贝内容
strcpy(name,n);
}
Dog(const Dog& d)
{
name = new char[20];
// 只拷贝内容
strcpy(name,d.name);
}
// 析构函数
~Dog()
{
delete name;
}
void show_name()
{
cout << name << endl;
}
};
int main()
{
char c[20] = "wangcai";
Dog d1(c); // d1.name → 堆区1
Dog d2(d1); // d2.name → 堆区2
strcpy(c,"tiedan");
d1.show_name(); // wangcai
d2.show_name(); // wangcai
return 0;
}
6.作用域限定符::
6.1名字空间
std是C++标准库的一个名字空间,很多使用的内容都是来自于标准名字空间,例如字符串std::string、std::cout...
当项目中包含using namespace std;时,代码中使用std名字空间中的内容就可以省略前面的std::
#include <iostream>
using namespace std;
int a = 1;
// 自定义名字空间
namespace my_space
{
int a = 3;
int b = 4;
}
// 使用自定义名字空间
using namespace my_space;
int main()
{
int a = 2;
cout << a << endl; // 2
cout << ::a << endl; // 1
cout << my_space::a << endl; // 3
// 完整写法
std::string s = "hhh";
std::cout << s << std::endl;
// 实际上是my_space::b
cout << b << endl; // 4
return 0;
}
6.2类内声明,类外定义
对于类中的成员也可以声明定义分离,如果声明定义分离,通常在类内声明,在类外定义,类外的定义需要结合作用域限定符使用。
#include <iostream>
using namespace std;
class Test
{
private:
string str = "随便";
public:
// 类内声明
Test(); // 无参构造
string get_str(); // getter
};
// 类外定义
string Test::get_str()
{
return str; // 也属于类内
}
Test::Test()
{
cout << "构造函数" << endl;
}
int main()
{
Test t;
cout << t.get_str() << endl;
return 0;
}
6.3与static关键字配合使用
在后续笔记中添加补充
7.构造函数的隐式调用
如果赋值时,刚好赋值运算符右边的数值是左边类的构造函数可以接收的类型,编译器则会自动调用这个构造函数并把赋值运算符右边的数值传入构造函数中,相当于隐式调用了构造函数。
#include <iostream>
using namespace std;
class Cow
{
private:
string name;
public:
Cow(string n):name(n)
{
cout << "生了一头牛:" << name << endl;
}
string get_name()
{
return name;
}
};
int main()
{
int i1(1);
string s = "小花"; // 直接写双引号的字符串实际上是const char[]
Cow c1(s);
int i2 = 2;
s = "小黑";
Cow c2 = s; // 隐式调用构造函数
cout << c1.get_name() << endl;
cout << c2.get_name() << endl;
return 0;
}
有时候在一些参数传递的过程中,隐式构造可能会在程序员无意的情况下触发了构造函数,因此可以在构造函数前添加explicit关键字修饰,屏蔽隐式调用的用法。
public:
explicit Cow(string n):name(n)
{
cout << "生了一头牛:" << name << endl;
}
8.this指针
8.1概念
this指针是一个特殊的指针,保存的是当前类的对象首地址。
#include <iostream>
using namespace std;
class Test
{
public:
void test_this()
{
cout << this << endl;
}
};
int main()
{
Test t1;
cout << &t1 << endl; // 0x61fe8b
t1.test_this(); // 0x61fe8b
Test* t2 = new Test;
cout << t2 << endl; // 0xae2350
t2->test_this(); // 0xae2350
delete t2;
return 0;
}
实际上可以通过下面的方法判断this的指向:
this所在的函数是哪个对象的,this指向的就是这个对象。
8.2原理
在类内调用此类的成员,虽然不用手写this指针,但是编译器都会使用this指针来调用成员,因为成员只能由对象来调用,而this指针指向的就是当前类的对象。
#include <iostream>
using namespace std;
class Student
{
private:
string name;
public:
Student(string s)
{
this->name = s;
}
string get_name()
{
return this->name;
}
void show()
{
cout << this->get_name() << endl;
}
};
int main()
{
Student s("张三");
s.show(); // 张三
return 0;
}
8.3应用
利用this指针的原理,其应用有:
● 区分重名的成员变量与局部变量
● 链式调用
● 多态传参
8.3.1区分重名变量
当成员变量与局部变量重名时,可以使用this指针调用成员变量。
#include <iostream>
using namespace std;
class Student
{
private:
string name;
public:
Student(string name)
{
this->name = name;
}
string get_name()
{
return name;
}
};
int main()
{
Student s("张三");
cout << s.get_name() << endl;
return 0;
}
8.3.2链式调用
如果一个函数的返回值是当前类的引用,那么通常此函数需要返回一个*this,并且此函数支持链式调用。
#include <iostream>
using namespace std;
class Value
{
private:
int data = 0;
public:
int get_data()
{
return data;
}
/**
* @brief add 加法
* @param i 增加的数值
* @return
*/
Value& add(int i)
{
data += i;
return *this; // *this表示的是当前类的对象本身
}
};
int main()
{
Value v;
// 链式调用 0+1+2+3+4+5
v.add(1).add(2).add(3).add(4).add(5);
cout << v.get_data() << endl; // 15
// 相当于
v.add(1);
v.add(2);
v.add(3);
v.add(4);
v.add(5);
cout << v.get_data() << endl; // 30
return 0;
}
8.3.3多态传参
等待后续补充。
9.static关键字
static关键字在类内有以下几种用法:
● 静态局部变量
● 静态成员变量
● 静态成员函数
9.1静态局部变量
使用static关键字修饰局部变量就是静态局部变量。
#include <iostream>
using namespace std;
class Test
{
public:
void test_static()
{
int a = 1;
// 静态局部变量
static int b = 1;
cout << ++a << " " << ++b << endl;
cout << &a << " " << &b << endl;
}
};
int main()
{
Test t1;
t1.test_static();
cout << endl;
t1.test_static();
return 0;
}
运行结果:
虽然上面的结果中,变量a和变量b的地址始终都没有变化,但是变量a在这个地址上创建了两次,而变量b只在此函数第一次调用时创建了一次。
实际上,此类中所有的对象都共享一个静态变量,把上面的代码主函数更改为:
int main()
{
Test t1;
t1.test_static();
cout << endl;
Test t2;
t2.test_static();
return 0;
}
运行结果不变。
静态局部变量所在的函数第一次被调用时,静态局部变量创建,在程序结束时才销毁。
9.2静态成员变量
成员变量使用static修饰就是静态成员变量,静态成员变量具有以下特点:
● 此类的所有对象共用此变量
● 非const的静态成员变量通常需要类内声明,类外初始化
● 静态成员变量可以直接使用类名::来调用,更推荐使用此方式
● 静态成员变量在程序运行时创建,在程序结束时销毁
#include <iostream>
using namespace std;
class Test
{
public:
static int a; // 类内声明
};
int Test::a = 1; // 类外初始化
int main()
{
cout << Test::a << " " << &Test::a << endl; // 1 0x408004
Test t1;
cout << ++t1.a << " " << &t1.a << endl; // 2 0x408004
Test t2;
cout << t2.a << " " << &t2.a << endl; // 2 0x408004
return 0;
}
9.3静态成员函数
成员函数使用static修饰就是静态成员函数,静态成员函数的特点有:
● 静态成员函数不能访问此类中非静态成员,因为没有this指针
● 静态成员函数只能调用本类中静态的成员
● 非静态成员函数可以调用静态成员
● 除了可以使用当前类对象调用静态成员函数外,也可以直接使用类名::调用,推荐后者
● 如果静态成员函数声明与定义分离,只需要在声明处使用static修饰
#include <iostream>
using namespace std;
class Test
{
public:
static int a;
void method()
{
cout << "非静态成员" << endl;
}
static void func()
{
// method(); 错误
cout << a << endl;
}
static void func2(); // 声明
};
int Test::a = 1;
void Test::func2()
{
cout << "静态成员函数" << endl;
}
int main()
{
Test::func();
Test::func2();
Test t;
t.func();
return 0;
}