1.类与对象的概念
1.概念
类:类是对同一类对象的抽象总结,是一个概念。
对象:按照类的规定创建的实体。
2.面向对象编程
需要先写 类 的代码,才能按照这段代码创建对应的 对象 。
参考类来创建对象的过程被称为“实例化”。对象无法脱离对应的类存在,调用成员离不开对象。
2.类
1.类包括:
属性:用来描述 对象 的数据元素,通常是一个名词。
也称为“成员变量”或“数据成员”
行为:用来描述 对象执行的 具体操作 ,通常对属性进行操作。以动词函数的方式存在
也称为“成员函数”或“成员方法”
成员变量和成员函数->统称为“成员”
2.类的定义:
eg:手机类
手机的属性:品牌、型号、重量
手机的成员函数:运行游戏、播放音乐、通信
#include <iostream>
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()
{rrreturn 0;}
3.实例化对象
类是一个抽象的概念,因此需要按照这个概念创建对应的对象实体。C++两种类型的对象:
1.栈内存对象
生命周期:在生命周期结束(所在的花括号执行完)后,自动被销毁
调用成员:使用 . 调用成员
2. 堆内存对象
创建+销毁:使用new关键字创建,使用delete关键字销毁,
(如果不销毁,则会持续存在,容易导致内存泄漏的问题,内存泄漏最终可能会导 致程序卡顿,甚至卡死。)
堆内存对象通常使用指针来保存堆内存对象的地址。
调用成员:使用 -> 调用成员(在Qt Creator下,直接打.会转换成 ->)
int main()
{
MobilePhone mp1; //栈内存对象
//调用属性
mp1.brand = "小米";
mp1.model = "13Pro";
mp1.weight = 216;
//调用成员函数
mp1.communicate();
mp1.play_music();
mp1.run_game();
MobilePhone* mp2 = new MobilePhone;//堆内存对象
//调用属性
mp2->brand = "红米";
mp2->model = "K60 Pro";
mp2->weight = 206;
//调用成员函数
mp2->communicate();
mp2->play_music();
mp2->run_game();
delete mp2; mp2 = NULL;
return 0;
}
4.封装
封装要求将类的一些属性和细节隐藏,并重新公开给外部调用的接口。
写法不唯一。通常先对属性私有化,使属性隐藏。然后根据当前属性的需求,通过getter函数和setter函数对类外分别公开读和写的功能 。
封装有利于提升程序的安全性。
在类内:private: // 私有权限:只有类内部可访问
public: // 公开接口
5.构造函数
5.1概念
构造函数是类内一种特殊的函数,用来创建一个对象。
1)如果一个类中,程序员不手动编写构造函数,编译器会为这个类自动添加一个无参构造函数, 且此函数的函数体为空;
2)如果程序员手写了任意一个构造函数,编译器就不再自动添加构造函数了。
函数名:构造函数要求函数名必须与类名完全一致,且构造函数无需写返回值。
构造函数也支持重载、参数默认值,可以通过给构造函数增加参数,使用参数给属性赋予初始值。
5.2构造初始化列表
构造初始化列表是一种简便的写法,可以用于给属性赋予初始值。
MobilePhone(string b ,string m ,int w )
:brand(b),model(m),weight(w){}
5.3拷贝构造函数
5.3.1概念
如果程序员在一个类中不手动编写拷贝构造函数,编译器会为这个类自动添加一个拷贝构造函数。
拷贝构造函数与普通构造函数也是函数重载的关系。
对象和对象之间是独立存在的实体,数据也是相互独立的,拷贝构造函数可以把一个对象的属性值拷贝到新创建的对象中。
拷贝构造函数可能会出现以下的问题:
如果成员变量出现指针,在拷贝的过程中,会导致两个对象的成员变量指针保存同一份地址,指向同一个内存区域,不符合面向对象的特性。
5.3.2浅拷贝
当类中的成员变量出现【指针】,默认的浅拷贝,代码如下:
#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;
}
5.3.3深拷贝
1. 此时需要手写构造函数,为每个对象的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;
}
2. 其实在实际开发中,也可以采用屏蔽拷贝构造函数这种简单粗暴的方式解决浅拷贝问题。屏蔽某个构造函数只需要把这个构造函数的代码从public区域移动到private区域。
6.析构函数
析构函数是与构造函数对立的函数。
构造函数 | 析构函数 |
手动调用 | 在对象被销毁时【自动】调用 |
通常用于在对象创建时初始化 | 通常用于在对象销毁时回收资源 |
可以被重载 | 不可以重载,因为没有参数 |
函数名是类名 | 函数名是~类名 |
示例代码如下 :
#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;
}
7.作用于限定符::
7.1名字空间
std:
std是C++标准库的一个名字空间,很多使用的内容都是来自于标准名字空间,例如字符串std::string、std::cout...
当项目中包含using namespace std;时,代码中使用std名字空间中的内容就可以省略前面的std::
// 自定义名字空间
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;
}
7.2 类内声明,类外定义
对于类中的成员也可以声明定义分离。
如果声明定义分离,通常在类内声明,在类外定义。
类外的定义需要结合作用域限定符使用。
class Test
{
private:
string str = "随便";
public:
// 类内声明
Test(); // 无参构造
string get_str(); // getter
};
// 类外定义
string Test::get_str()
{
return str; // 也属于类内
}
Test::Test()
{
cout << "构造函数" << endl;
}
8. explicit关键字
如果赋值时,刚好赋值运算符右边的数值是左边类的构造函数可以接收的类型,
编译器则会自动调用这个构造函数并把赋值运算符右边的数值传入构造函数中,
相当于【隐式调用了构造函数】。
//构造函数Cow(string n):name(n)
{
cout << "生了一头牛:" << name << endl;
}
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关键字修饰,屏蔽隐式调用的用法。
9. this指针
9.1概念
this指针是一个特殊的指针,保存的是当前类的对象首地址。
可以通过下面的方法判断this的指向:
this所在的函数是哪个对象的,this指向的就是这个对象。
9.2 原理
在类内调用此类的成员,虽然不用手写this指针,但是编译器都会使用this指针来调用成员,因为成员只能由对象来调用,而this指针指向的就是当前类的对象。
9.3 应用
利用this指针的原理,其应用有:
- 区分重名的成员变量与局部变量
当成员变量与局部变量重名时,可以使用this指针调用成员变量。
- 链式调用:
如果一个函数的返回值是当前类的引用,那么通常此函数需要返回一个*this,并且此函数支持链式调用。
- 多态传参
10. static关键字
static关键字在类内有以下几种用法:
- 静态局部变量
- 静态成员变量
- 静态成员函数
10.1 静态局部变量
使用static关键字修饰局部变量就是静态局部变量。
此类中所有的对象都共享一个静态变量
静态局部变量所在的函数第一次被调用时,静态局部变量创建,在程序结束时才销毁。
class Test
{
public:
void test_static()
{
int a = 1;
// 静态局部变量
static int b = 1;
cout << ++a << " " << ++b << endl;
cout << &a << " " << &b << endl;
}
};
变量a在这个地址上创建了两次,而变量b只在此函数第一次调用时创建了一次。
10.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;
}
10.3 静态成员函数
成员函数使用static修饰就是静态成员函数,静态成员函数的特点有:
- 静态成员函数不能访问此类中非静态成员,因为没有this指针
- 静态成员函数只能调用本类中静态的成员
- 非静态成员函数可以调用静态成员
- 除了可以使用当前类对象调用静态成员函数外,也可以直接使用类名::调用,推荐后者
- 如果静态成员函数声明与定义分离,只需要在声明处使用static修饰
11. const关键字
在C++中,虽然认为const表示常量的意思,但是严格地讲,const并不是常量。因为C++中const只能在程序的运行期间只读,即在编译期可以改变数值。
11.1 常成员函数
const修饰的成员函数,表示常成员函数,这种函数的特点是:
- 可以调用本类中非const的成员变量,但是不能修改其数值
- 不能调用非const的成员函数
建议成员函数只要不修改成员变量值就写为常成员函数,例如getter
11.2 常量对象
const修饰对象,表示该对象为常量对象,其特点有:
- 常量对象的任何属性值不能被修改
- 常量对象不能调用任何非const的成员函数
const修饰对象时,const关键字可以写在类名前面,也可以类名后面。
11.3 常成员变量
使用const修饰成员变量,表示该成员变量为常成员变量,其特点有:
- 程序运行时,常成员变量的值不可变
- 不能在函数体中赋值,只能通过直接赋值或构造初始化列表赋值
11.4 修饰局部变量
类似于之前给引用参数增加const修饰,函数的局部变量都可以使用const修饰,表示常量。
class Test{
public:
void func(const int a)
{
cout << a << endl;
// a++; 错误
const int i = 1;
cout << i << endl;
// i++; 错误
}
};