C++基础2:类与对象
1、认识类与对象
1.1 什么是类
类(class)是类型(type),是用户自定义的类型。为什么不叫它type,因为借用Simula语言中的class关键字
1.2 为什么要有类
基于便理性的考虑,现实世界中物(object)通常被分为几种独立的分类。
1.3 基本概念
概念 | 比喻 |
---|---|
对象/实例 | 楼房 |
实例化 | 建造 |
类 | 建筑图纸 |
1.4 面向对象四大特征
特征 | 说明 | 类比 |
---|---|---|
抽象 | 抽出具体事物的普遍性的本质 | 分门别类:鸟类、哺乳类、鱼类 |
封装 | 把数据与处理(函数)包在一起 | 通讯录(增加、删除) |
继承 | 数据与处理(函数)的传承 | 财富与绝技、混血儿(肤色/头发、两种语言) |
多态 | 同一个事物(函数)的多种形态 | 手机键盘数字与字母、电脑键盘功能键 |
2、类的定义与对象创建
2.1 类的定义:与struct相似(C++)
class 类名{
成员变量成员函数声明
};
class定义最后的;一定不能忘记
2.2 构成
(1)基本构成
构成 | 作用 |
---|---|
数据成员(data member)/成员变量/属性 | 对象内部数据和状态,只能在类定义中声明,可以在成员函数中直接调用 |
成员函数/方法 | 对象相关的操作,可以在类内实现或者类外实现 |
(2)实例
复数complex
(3)成员函数实现与类定义分离
作用域运算符::函数归属
(4)访问限定符
限定符号 | 作用 |
---|---|
private(默认) | 私有 |
public | 公开 |
protected | 保护 |
实践中,成员变量多数情况使用private或者protected,成员函数多数使用pbulic。通常,通过成员函数改变对象的成员变量
(5)类定义与类实现分离
头文件声明
方式:#pragma once或者#ifnde…#endif
作用:防止头文件二次编译
源文件 – 实现
引用头文件:#include <>(标准库函数)/#include “”(自定义/第三方函数)
在C++书籍中为了方便.h与.cpp不做分离,但是项目开发中,需要分开。
对象做参数和返回值
Complex Add(Complex c1,Complex c2);
Complex Complex::Add(Complex c);
(6)class与struct区别
#include <iostream>
using namespace std;
struct Spos{
int x,y,z;
};
class Cpos{
public:
int x,y,z;
};
int main(){
#ifdef STRUCT
Spos spos = {
1,1,1};
cout <<"sasdasd"<< "("<<spos.x << ","<<spos.y<< ","<<spos.z<<")"<<endl;
#else
Cpos cpos={
1,1,1};
cout <<"cpos"<< "("<<cpos.x << ","<<cpos.y<< ","<<cpos.z<<")"<<endl;
#endif
}
(7)C++中class与struct区别
(1)默认的访问控制不同
struct是public,class是private
(2)初始化
struct可以使用花括号内的初始值列表{…}初始化,class不可以(C++98不可以,C++11可以)
(3)注意
C++的struct可以有成员函数,而C不可以。
C++的struct可以使用访问控制关键字(public private protected),而C不可以。
SPos spos; // C++
struct SPos spos; // C/C++
成员变量默认初始化为随机值(主要影响指针)。
2.3 对象创建/实例化
直接创建 – 类作为类型定义变量 – 栈上创建
(1)举例
基本类型:
int main(){
int a = 10;
int b(10);
cout << a << " " << b << endl;
}
基本类型的初始化新增语法:
int a(0);// 等价 int a = 0;
const float b(1.0);// 等价 const float b = 1.0;
类类型
// 定义类
class Demo{
};
// 创建对象
int main(){
Demo d; // 变量(命名对象)
Demo(); // 匿名对象
}
基本语法
类名 对象名; // 调用默认构造函数
类名(); // 创建匿名对象
(2)动态创建-堆上创建
例如
基本类型
int* p = new int;
delete p;
p = NULL;
类类型
// 定义类
class Demo{
};
// 创建对象
int main(){
Demo* d = new Demo;
delete d;
d = NULL;
}
基本语法
类名* 对象指针 = new 类名;// 调用默认构造函数
delete 对象指针;
对象指针new可以为对象设置初始值,例如下面代码
int* p = new int(100);
cout << *p << endl;
(3)动态创建数组–堆上创建
基本类型
int* pa = new int[10];
delete pa;// 只释放p[0]
delete [] pa;// 释放全部数组
类类型
// 定义类
class Demo{
};
// 创建对象
int main(){
Demo* d = new Demo[10];
delete [] d;
d = NULL;
}
对象数组指针new不可以为对象设置初始值。
int* pa = new int[10](100); // error: array 'new' cannot have initialization arguments
注意:C++除了特殊情况,很少直接使用malloc()/free()申请释放内存,取而代之的是new/delete。
3、this指针
作用域:
类内部
特点:
(1)类的一个自动生成、自动隐藏的私有成员
(2)每个对象仅有一个this指针
(3)当一个对象被创建时,this指针就存放指向对象数据的首地址
(4)不是对象本身的一部分,不会影响sizeof(对象)的结果
如果成员函数形参与成员变量同名,使用this->做为前缀区分。
4、方法
4.1 构造函数
(1)语法
类名(参数){
函数体
}
(2)特点
1、在对象被创建时自动执行
2、构造函数的函数名与类名相同
3、没有返回值类型、也没有返回值
4、可以有多个构造函数
(3)调用时机
对象直接定义创建–构造函数不能被显式调用
new动态创建
(4)默认构造函数
类中没有显式的定义任何构造函数,编译器就会自动为该类型生成默认构造函数,默认构造函数没有参数。
(5)构造函数的三个作用
1、给创建的对象建立一个标识符
2、为对象数据成员开辟内存空间
3、完成对象数据成员的初始化
4.2 初始化列表
(1)语法
类名(参数):成员变量(参数){
函数体
}
(2)作用
初始化非静态成员变量
(3)说明
从概念上来讲,构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。
必须使用初始化列表的情况
1、常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
2、引用类型,引用必须在定义的时候初始化,并不能重新赋值,所以也要写在初始化列表里面
3、没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
初始化列表与构造函数内部成员变量赋值的区别
成员变量初始化与成员变量赋值
能使用初始化列表的时候尽量使用初始化列表
(4)成员变量的初始化顺序
#include <iostream>
using namespace std;
class Member1{
public:
Member1(){
cout << "Member1 Init" << endl;
}
};
class Member2{
public:
Member2(){
cout << "Member2 Init" << endl;
}
};
class Member3{
public:
Member3(){
cout << "Member3 Init" << endl;
}
};
class Test{
public:
Test():m3(),m2(),m1(){
};
private:
Member1 m1;
Member2 m2;
Member3 m3;
};
int main(){
Test test;
}
(5)C++的函数可以增加默认参数。
写法:
1、默认参数必须写在函数声明中,不能写在函数实现的参数列表中
2、默认参数必须写在所有非默认参数的后面
3、默认参数可以写任意多个
使用:
1、默认参数可以不用传递值,此时,使用默认值
2、默认参数可以传值,此时,使用实参值
4.3 析构函数
问题:银行管理多个账户的内存泄露及解决方案
(1)语法
~类名(){
函数体
}
注意:
1、析构函数的函数名与类名相同
2、函数名前必须有一个~
3、没有参数
4、没有返回值类型、也没有返回值
5、只能有一个析构函数
(2)调用时机
1、对象离开作用域
2、delete
(3)默认析构函数
类中没有显式的定义析构函数,编译器就会自动为该类型生成默认析构函数
(4)作用
释放对象所申请占有的资源
RAII(资源的取得就是初始化,Resource Acquisition Is Initialization)
C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。
(5)分析下面程序执行结果
#include <iostream>
using namespace std;
class Test{
public:
Test(){
cout << "Test Construct" << endl;
}
~Test(){
cout << "Test Deconstruct" << endl;
}
};
int main(){
//局部对象
cout << "Before {" <<endl;
{
cout << "After {" << endl;
Test t;
cout << "Before }" << endl;
}
cout << "After }" << endl;
//动态对象
cout << "Before {" << endl;
{
cout << "After {" << endl;
Test *pt = new Test;
delete pt;
pt = NULL;
cout << "Before }" << endl;
}
cout << "After }" << endl;
return 0;
}
输出结果:
Before {
After {
Test Construct
Before }
Test Deconstruct
After }
Before {
After {
Test Construct
Test Deconstruct
Before }
After }
4.4 引用(别名)
(1)语法
声明:const 类型名& 对象名/类型名& 对象名
使用:与对象变量、基本类型变量一样
例如:
int a = 1
int& b = a;
cout << "&a:" << &a << endl;
cout << "&b:" << &b << endl;
b = 13;
cout << a << endl;
引用其实就是一个别名,a与b代表的是相同的对象。
(2)何处使用引用
1、函数参数列表
2、函数返回值
3、成员变量–对象初始化时,必须显示初始化的变量
(3)为何使用引用
1、避免对象复制
2、避免传递空指针
3、使用方便
类型:
1、基本类型(bool、char、short、float)
2、复合类性(指针、数组、引用)
3、自定义类型(struct、union、class)
(4)引用作用
取代指针
#include <iostream>
using namespace std;
void Func(int *n){
*n = 2;
}
void Func(int & n){
n=3;
cout << &n << endl;
}
int main(){
int a =1;
cout << &a << endl;
Func(&a);
cout << a << endl;
Func(a);
cout << a << endl;
}
输出结果:
0x7ffdeb40f64c
2
0x7ffdeb40f64c
3
函数三种传参方式:
1、传值void Func(int n)
2、传地址/指针void Func(int *n)
3、传引用void Func(int &n)
(5)引用与指针的区别
1、指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名
2、引用只能在定义时被初始化一次,之后不可变;指针可变
3、引用不能为空,指针可以为空
4、引用使用时无需解引用*,指针需要解引用
5、sizeof引用得到的是所指向的变量/对象的大小,而sizeof指针得到的是指针本身的大小
6、对于引用类型的成员变量,所属类的大小时按照指针大小计算,自身大小按照自身类型计算
引用通常适用于三种情形:成员变量和函数的参数列表以及函数返回值
4.5 拷贝/复制构造函数
(1)语法
类名(类名& 形参){
函数体
}
或者
类名(const 类名& 形参){
函数体
}
(2)调用时机
手动调用
类名 对象名; // 调用默认构造函数
类名 对象2 = 对象1; // 调用复制构造函数
类名 对象3(对象1); // 调用复制构造函数
自动调用
1、一个对象作为函数参数,以值传递的方式传入函数体
2、一个对象作为函数返回值,以值从函数返回
3、 一个对象拷贝构造,它的成员对象自动调用拷贝构造
4、子对象拷贝构造父对象自动拷贝构造
实践说明:
1、gcc/clang自动RVO/NRVO优化,不执行拷贝构造函数。可以在编译命令添加选项禁止-fno-elide-constructors;
2、VC在Debug环境下返回值执行拷贝构造函数,在Release环境下实施RVO/NRVO优化。
4.6 默认拷贝构造函数
本质:内存拷贝
作用:复制一个已经存在的对象
(1)运行结果
#include <iostream>