2:类与对象
2.1 认识类与对象
- 什么是类(
class
)?
类(class
)是类型(type
),是用户自定义的类型。为什么不叫它type,因为借用Simula
语言中的class关键字。
- 为什么要有类?
基于便利性的考虑,现实世界中物(object)通常被分为几种独立的分类。
- 基本概念
概念 |
比喻 |
对象/实例 |
楼房 |
实例化 |
建造 |
类 |
图纸 |
特征 |
说明 |
类比 |
抽象 |
抽出具体事物的普遍性的本质 |
分门别类:鸟类、哺乳类、鱼类 |
封装 |
把数据与处理(函数)包在一起 |
通信录(增加、删除) |
继承 |
数据与处理(函数)的传承 |
财富与绝技、混血儿(肤色/头发)、 两种语言 |
多态 |
同一个事物(函数)的多种形态 |
手机键盘数字与字母、 电脑键盘功能键 |
2.2. 类的定义与对象创建
2.2.1 类的定义:与struct
相似(C++
)
class 类名{
成员变量成员函数声明
};
class
定义最后的;
一定不要忘记。
构成 |
作用 |
数据成员(data member)/成员变量/属性 |
对象内部数据和状态,只能在类定义中声明,可以在成员函数中直接调用。 |
成员函数/方法 |
对象相关的操作,可以在类内实现或类外实现。 |
限定符 |
作用 |
private [默认] |
私有 |
public |
公开 |
protected |
保护 |
注:实践中,成员变量多数情况使用private或者protected,成员函数多数情况使用public。通常,通过成员函数改变对象的成员变量。
- 类定义与类实现分离
1、头文件 – 声明
方式:#pragma once
或者#ifnde...#endif
作用:防止头文件二次编译
实例:
complex.h
#ifndef __COMPLEX_H
#define __COMPLEX_H
class Complex{
private:
int real;
int imag;
public:
void SetReal(int r);
void SetImag(int i);
int GetReal();
int GetImag();
void Print();
Complex Add(Complex c1);
};
Complex Add(Complex c1,Complex c2);
#endif //__COMPLEX_H
2、源文件 – 实现
引用头文件:#include <>
(标准库函数)/#include ""
(自定义/第三方函数)
注:在C++书籍中为了方便.h与.cpp不做分离,但是项目开发中,需要分开。
Complex Add(Complex c1,Complex c2);
Complex Complex::Add(Complex c);
complex.cpp
:
#include <iostream>
#include "complex.h" //引用头文件
using namespace std;
void Complex::SetReal(int r) {
real = r;
}
void Complex::SetImag(int i) {
imag = i;
}
int Complex::GetReal() {
return real;
}
int Complex::GetImag() {
return imag;
}
void Complex::Print() {
cout << real << "+" << imag << "i" << endl;
}
Complex Complex::Add(Complex c1) {
Complex res;
//成员函数可以直接访问自身的成员变量
//res.SetReal(real+c1.GetReal());
//res.SetImag(imag+c1.GetImag());
//成员函数可以直接访问自身的成员函数
//res.SetReal(GetReal()+c1.GetReal());
//res.SetImag(GetImag()+c1.GetImag());
//在成员函数中,当前类作为参数传入时,对象可以直接访问自身的私有成员
//res.SetReal(real+c1.real);
//res.SetImag(imag+c1.imag);
res.real = real + c1.GetReal();
res.imag = imag + c1.GetImage();
return res;
}
Complex Add(Complex c1,Complex c2) {
Complex c;
c.SetReal(c1.GetReal()+c2.GetReal());
c.SetImag(c1.GetImag()+c2.GetImag());
return c;
}
main.c
:
#include "complex.h"
int main(){
Complex c;
c.SetReal(3);
c.SetImag(5);
c.Print();
Complex c2;
c2.SetReal(6);
c2.SetImag(8);
c2.Print();
Complex c3 = Add(c,c2);
c3.Print();
Complex c4 = c.Add(c2);
c4.Print();
}
实例1:复数(未拆分)
#include <iostream>
using namespace std;
class Complex{
private:
int real;
int imag;
public:
void SetReal(int r){
real = r;
}
void SetImag(int i){
imag = i;
}
int GetReal(){
return real;
}
int GetImag(){
return imag;
}
void Print(){
cout << real << "+" << imag << "i" << endl;
}
Complex Add(Complex c1){
Complex res;
//成员函数可以直接访问自身的成员变量
//res.SetReal(real+c1.GetReal());
//res.SetImag(imag+c1.GetImag());
//成员函数可以直接访问自身的成员函数
//res.SetReal(GetReal()+c1.GetReal());
//res.SetImag(GetImag()+c1.GetImag());
//在成员函数中,当前类作为参数,该对象可以直接访问自身的私有成员(c1和当前类属于同一类型,且只能在成员函数中访问)
res.SetReal(real+c1.real);
res.SetImag(imag+c1.imag);
//res.real = real + c1.GetReal();
//res.imag = imag + c1.GetImage();
return res;
}
};
Complex Add(Complex c1,Complex c2){
Complex c;
c.SetReal(c1.GetReal()+c2.GetReal());
c.SetImag(c1.GetImag()+c2.GetImag());
return c;
}
int main(){
Complex c;
c.SetReal(3);
c.SetImag(5);
c.Print();
Complex c2;
c2.SetReal(6);
c2.SetImag(8);
c2.Print();
Complex c3 = Add(c,c2);
c3.Print();
Complex c4 = c.Add(c2);
c4.Print();
}
实例2:账单
#include <iostream>
#include <cstring>
using namespace std;
class Bill{
public:
void SetName(const char* s){
strcpy(name,s);
}
void SetNum(int n){
num = n;
}
void SetPrice(float p){
price = p;
}
float GetTotalPrice(){
return num*price;
}
void Print(){
cout << name << '\t' << num << "\t¥" << price << '\t' << GetTotalPrice() << endl;
}
void Scan(){
cin >> name >> num >> price;
}
int GetNum(){
return num;
}
private:
char name[20];
int num;
float price;
};
int main(){
int n;
cin >> n;
Bill b[n];
for(int i = 0;i < n;++i){
b[i].Scan();
}
cout << "商品\t数量\t单价\t小计" << endl;
float sum(0);
int num(0);
for(int i = 0;i < n;++i){
num += b[i].GetNum();
sum += b[i].GetTotalPrice();
b[i].Print();
}
cout << "----------------------------------" << endl;
cout << "共计" << num << "件\t\t\t¥" << sum << endl;
}
2.2.2 C++class
与struct
区别
- 1、默认的访问控制不同
struct
是public
,class
默认是private
- 2、
struct
可以使用花括号内的初始值列表{...}
初始化,class
不可以(C++98不可以,C++11可以)。
注:
(1)C++
的class
可以有成员函数,而C
不可以。
(2)C++
的class
可以使用访问控制关键字(public private protected)
,而C
不可以。
(3)C++
的class
成员变量默认初始化为随机值(主要影响指针)。
2.2.3 对象创建/实例化
例如:
1、基本类型
//c
int a = 10;
int b(10);
//c++
int a(0);// 等价 int a = 0;
const float b(1.0);// 等价 const float b = 1.0;
2、类类型
// 定义类
class Demo{};
// 创建对象
int main(){
Demo d; // 变量(命名对象)
Demo(); // 匿名对象
}
基本语法:
类名 对象名; // 调用默认构造函数
类名(); // 创建匿名对象
1、基本类型
int* p = new int;
delete p;
p = NULL;
2、类类型
// 定义类
class Demo{};
// 创建对象
int main(){
Demo* d = new Demo;
delete d;
d = NULL;
}
基本语法:
类名* 对象指针 = new 类名;// 调用默认构造函数
delete 对象指针;
对象指针new
可以为对象设置初始值,例如下面代码
int* p = new int(100);
cout << *p << endl;
1、基本类型
int* pa = new int[10];
delete pa;// 只释放p[0]
delete [] pa;// 释放全部数组
2、类类型
// 定义类
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。
2.3 this
指针
作用域:类内部
特点:
1、类的一个自动生成、自动隐藏的私有成员
2、每个对象仅有一个this
指针
3、当一个对象被创建时,this
指针就存放指向对象数据的首地址
4、不是对象本身的一部分,不会影响sizeof(对象)
的结果
注:如果成员函数形参与成员变量同名,使用this->做为前缀区分。
2.4方法
2.4.1 构造函数
类名(参数){
函数体
}
Complex():real(0),imag(0){}
- 特点:
1、在对象被创建时自动执行
2、构造函数的函数名与类名相同
3、没有返回值类型、也没有返回值
4、可以有多个构造函数
- 调用时机
1、对象直接定义创建–构造函数不能被显式调用
2、new
动态创建
- 默认构造函数
类中没有显式的定义任何构造函数,编译器就会自动为该类型生成默认构造函数,默认构造函数没有参数。
- 构造函数的三个作用
1、给创建的对象建立一个标识符
2、为对象数据成员开辟内存空间
3、完成对象数据成员的初始化
- 构造函数初始化成员列表
语法:
类名(参数):成员变量(参数){
函数体
}
实例:
Complex(int r,int i):real(r),imag(i){}
作用:
初始化非静态成员变量
说明:
从概念上来讲,构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。
加入构造函数后,对象可以用以下方式定义并初始化:
#include "complex.h"
int main(){
int n(10); //int n - 10;
//Complex c;
//c.SetReal(3);
//c.SetImag(5);
Complex C(3,5);
c.Print();
/*
Complex c2;
c2.SetReal(6);
c2.SetImag(8);
*/
Complex c2(6,8);
c2.Print();
Complex c3 = Add(c,c2);
c3.Print();
Complex c4 = c.Add(c2);
c4.Print();
}
-
必须使用初始化列表的情况:
1、常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面。
2、引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面。
3、没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
注:能使用初始化列表的时候尽量使用初始化列表
2.4.2 析构函数
~类名(){
函数体
}
1、析构函数的函数名与类名相同
2、函数名前必须有一个~
3、没有参数
4、没有返回值类型、也没有返回值
5、只能有一个析构函数
#include <iostream>
using namespace std;
class Simple{
int n;
public:
Simple(int n):n(n){
cout << "n:" << n << endl;
this->n = n;
cout << "n:" << n << endl;
}
~Simple(){
cout << "~Simple()" << endl;
}
void SetN(int n){ //void SetN(Simple* this,int num){
this->n = n; // this->n = num;
} //}
int GetN(){
return n;
}
void Print(){
cout << this << ":" << n << endl;
}
};
int main(){
/*
Simple s1;
s1.SetN(1); //SetN(&s1,1);
cout << &s1 << endl;
s1.Print();
*/
Simple s(10);
}
2.4.3 引用
const 类型名& 对象名/类型名& 对象名
#include <iostream>
using namespace std;
void swap(int* a,int* b){
int t = *a;
*a = *b;
*b = t;
}
void swap2(int& a,int& b){
cout << "&a:" << &a << endl;
cout << "&b:" << &b << endl;
int t = a;
a = b;
b = t;
}
int main(){
int n = 10;
int* p = NULL;
p = &n;
cout << "*p:" << *p << endl;
cout << "n:" << n << endl;
*p = 20;
cout << "n:" << n << endl;
int& r = n;
cout << "r=" << r << endl;
r = 30;
cout << "r:" << r << endl;
cout << "n:" << n << endl;
cout << "*p:" << *p << endl;
//引用就是一个别名,n和r地址相同,对r操作相当于对n操作,也相当于对*p操作
cout << "&r:" << &r << endl;
cout << "&n:" << &n << endl;
cout << "p:" << p << endl;
int x = 10,y =20;
cout << "x:" << x << "\t" << "y:" << y << endl;
swap(&x,&y);
cout << "x:" << x << "\t" << "y:" << y << endl;
swap2(x,y);// int& a = x;int& b = y;
cout << "x:" << x << "\t" << "y:" << y << endl;
cout << "&x:" << &x << endl;
cout << "&y:" << &y << endl;
}
*p:10
n:10
n:20
r=20
r:30
n:30
*p:30
&r:0x7ffca409a4ac
&n:0x7ffca409a4ac
p:0x7ffca409a4ac
x:10 y:20
x:20 y:10
&a:0x7ffca409a4a8
&b:0x7ffca409a4a4
x:10 y:20
&x:0x7ffca409a4a8
&y:0x7ffca409a4a4
引用其实就是一个别名,a与b代表的是相同的对象。
- 何处使用引用
1、函数参数列表
2、函数返回值
3、成员变量 – 对象初始化时,必须显示初始化的变量
- 为何使用引用
1、避免对象复制
2、避免传递空指针
3、使用方便
- 引用作用
取代指针
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;
}
-
函数的三种传参方式
1、传值void Func(int n)
2、传地址/指针void Func(int* n)
3、传引用void Func(int& n)
-
引用与指针的区别
1、指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
2、引用只能在定义时被初始化一次,之后不可变;指针可变;
3、引用不能为空,指针可以为空;
4、引用使用时无需解引用*,指针需要解引用;
5、sizeof 引用
得到的是所指向的变量/对象的大小,而sizeof 指针
得到的是指针本身的大小;
6、对于引用类型的成员变量,所属类的大小时按照指针大小计算,自身大小按照自身类型计算。
2.4.4 拷贝/复制构造函数
类名(类名& 形参){
函数体
}
或
类名(const 类名& 形参){
函数体
}
1、手动调用
类名 对象名; // 调用默认构造函数
类名 对象2 = 对象1; // 调用复制构造函数
类名 对象3(对象1); // 调用复制构造函数
2、自动调用
(1)一个对象作为函数参数,以值传递的方式传入函数体
(2)一个对象作为函数返回值,以值从函数返回
(3)一个对象拷贝构造,它的成员对象自动调用拷贝构造
(4)子对象拷贝构造父对象自动拷贝构造
- 实例:分别采用C和C++实现栈的创建、出栈、入栈等操作
C语言写法stack.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int Element;
typedef struct Stack{
Element* data;
size_t size;
}Stack;
Stack* Create(){
Stack* s = malloc(sizeof(Stack));
s->data = NULL;
s->size = 0;
return s;
}
bool Push(Stack* s,Element e){
if(NULL == s){
return false;
}
s->data = realloc(s->data,sizeof(Element)*(s->size+1));
if(NULL == s->data) return false;
s->data[s->size] = e;
s->size++;
return true;
}
Element* Top(Stack* s){
if(NULL == s || NULL == s->data) return NULL;
return &s->data[s->size-1];
}
bool Pop(Stack* s){
if(NULL == s || NULL == s->data) return NULL;
s->size--;
s->data = realloc(s->data,sizeof(Element)*s->size);
if(s->data != NULL) return true;
else return false;
}
size_t GetSize(Stack* s){
return s->size;
}
void Destory(Stack** s){
if(NULL == *s || NULL == s) return;
free((*s)->data);
free(*s);
*s = NULL;
}
int main(){
Stack* s = Create();
for(;;){
Element e;
int num = scanf("%d",&e);
if(EOF == num) break;
Push(s,e);
}
while(GetSize(s) != 0){
printf("%d ",*Top(s));
Pop(s);
}
printf("\n");
Destory(&s);
}
C++写法 stack.cpp
#include <iostream>
#include <cstring>
using namespace std;
typedef int Element;
class Stack {
Element* data;
size_t size;
public:
// 构造函数
Stack(){
cout << this << ":Stack constructor" << endl;
data = NULL;
size = 0;
}
// 拷贝构造函数
Stack(const Stack& s){
cout << "Stack copy constructor" << endl;
size = s.size;
data = s.data;
}
// 析构函数
~Stack(){
cout << this << ":Stack destructor" << endl;
delete [] data;
data = NULL;
size = 0;
}
bool Push(Element e) {
Element* tmp = new Element[size+1];
if(NULL == tmp) return false;
memcpy(tmp,data,sizeof(Element)*size);
tmp[size] = e;
delete [] data;
data = tmp;
++size;
return true;
}
Element* Top() {
if(NULL == data) return NULL;
return &data[size-1];
}
bool Pop() {
if(NULL == data) return false;
size--;
Element* tmp = new Element[size];
if(NULL == tmp) return false;
memcpy(tmp,data,sizeof(Element)*size);
delete [] data;
data = tmp;
return true;
}
size_t GetSize() {
return size;
}
void Destory() {
delete [] data;
data = NULL;
size = 0;
}
};
int main() {
Stack s;
for(;;) {
Element e;
if(!(cin >> e)) break;
s.Push(e);
}
while(s.GetSize() != 0) {
cout << *s.Top() << " ";
s.Pop();
}
cout << endl;
s.Destory();
//Stack t = s; //调用拷贝构造函数
//Stack t(s); //拷贝构造
//Func(s); //Stack t = s; //拷贝构造
Stack t = Return(); //真实执行拷贝构造但是被编译器优化了
/* 近似操作
Stack ReturnVal;
{
Stack s;
s.Push(1);
s.Push(2);
s.Push(3);
ReturnVal = s; // 这里真实执行的拷贝构造
}
*/
//cout << "&t:" << &t << endl;
}
}
2.4.5 默认拷贝构造函数
- 本质:
内存拷贝
- 作用:
复制一个已经存在的对象
- 实例1:
#include <iostream>
#include <string>
using namespace std;
class Test {
public:
Test() {}
Test(Test &t) { cout << "Test(Test &t)" << endl; }
Test(Test const &t) { cout << "Test(Test const &t)" << endl; }
};
int main() {
Test t1;
Test t2 = t1;
const Test t3;
Test t4 = t3;
}
Test(Test &t)
Test(Test const &t)
#include <iostream>
#include <string>
// 禁用编译优化g++ test.cpp -fno-elide-constructors
using namespace std;
class Test {
public:
Test();
Test(const Test &t);
~Test();
//private:
// Test(const Test &t); //禁用拷贝构造函数
};
Test::Test() {
cout << this << ":constructor" << endl;
}
Test::Test(const Test &t) {
cout << this << ":copy constructor" << endl;
}
Test::~Test() {
cout << this << ":destructor" << endl;
}
Test Return() {
Test t;
return t; //NRVO: Name Return Value Option
//return Test(); //RVO: Return Value Option
}
int main() {
cout << "1" << endl;
{
cout << "2" << endl;
//Test t;
//Test(); //匿名对象 临时对象 生存周期只在当前语句
/*
Test* p = new Test; //手动创建
delete p; //需要手动销毁
p = NULL;
*/
Test t1 = Return();
cout << "3" << endl;
}
cout << "4" << endl;
}
1
2
0x7ffe3671285f:constructor
0x7ffe3671288f:copy constructor
0x7ffe3671285f:destructor
0x7ffe3671288e:copy constructor
0x7ffe3671288f:destructor
3
0x7ffe3671288e:destructor
4
2.5 赋值运算符重载函数
类名& operater=(const 类名& 形参){
// 赋值操作
return *this;
}
加上引用后可以有效的避免函数调用时对实参的拷贝,提高了程序的效率。
- 调用时机:
类对象进行赋值的时候
- 作用:
赋值
- 拷贝构造函数与赋值操作符的区别
1、拷贝构造函数:用一个已经存在的对象来初始化一个不曾存在的对象
2、赋值操作符:当两个对象都已经存在
- 实例1:
#include <iostream>
using namespace std;
class Simple{
public:
Simple(){
cout << "construtor" << endl;
}
explicit Simple(int n){ //explicit关键字,禁用构造函数的默认转换
cout << "construtor " << n << endl;
}
Simple(const Simple& s){
cout << "copy construtor" << endl;
}
//赋值运算符重载
Simple& operator=(const Simple& s){
cout << "assign" << endl;
return *this; // 返回当前对象
}
~Simple(){
cout << "destructor" << endl;
}
//禁用拷贝构造函数和赋值运算符重载的方法一:删除函数
//Simple(const Simple& s) = delete; // 禁用拷贝构造函数的方法
//Simple& operator=(const Simple& s) = delete; // 禁用赋值运算符重载的方法
//禁用拷贝构造函数和赋值运算符重载的方法二:加入私有
/*
private:
Simple(const Simple& s); // 禁用拷贝构造函数的方法
Simple& operator=(const Simple& s); // 禁用赋值运算符重载的方法
*/
};
void Func(Simple s){}
int main(){
Simple s;
Simple t;
Simple w;
w = t = s;
// t = s; //返回t
// w = t; //返回w
Simple n1(1);
Simple n2(2); //构造函数加入关键字后,只能这么写
//Simple n3 = 2; // 构造函数加入explicit后不支持这种写法
//类似操作:
//Simple temp(2);
//Simple n3 = temp;
//Func(3); // Simple s = 3; //explicit,禁用构造函数的默认转换
Func(Simple(3));
}
实例2:
#include <iostream>
using namespace std;
class Test{
public:
Test(int i){
cout << this << " constructor " << i << endl;
}
Test(const Test& t){
cout << this << " copy onstructor " << &t << endl;
}
//赋值运算符重载
Test& operator=(const Test& t){
cout << this << " assign " << &t << endl;
return *this;
}
~Test(){
cout << this << " destructor " << endl;
}
};
void Func(const Test& t){}
void Function(int a,int b = 1,int c = 2){
cout << a << " "<< b << " " << c << endl;
};
int main(){
//Test a(1);
//Test a = 1; //近似过程:Test(1);Test a = Test(1); //g++ test3.cpp -fno-elide-constructors可以看到构造过程
//Func(1); // 近似:cosnt Test& t = Test(1);
Test a(1);
a = 2;
Function(100);
Function(100,200);
Function(100,200,300);
}
0x7ffe4dd6974e constructor 1
0x7ffe4dd6974f constructor 2
0x7ffe4dd6974e assign 0x7ffe4dd6974f
0x7ffe4dd6974f destructor
100 1 2
100 200 2
100 200 300
0x7ffe4dd6974e destructor
2.6 深拷贝与浅拷贝
- 浅拷贝:
编译器默认生成的类实例间拷贝行为,对带有指针的类来说会引发 memory leak
(内存泄漏)。
- 深拷贝:
用户定义的行为(实质是一种构造函数)。
- 区别:
浅拷贝:只拷贝指针地址
深拷贝:重现分配堆内存,拷贝指针指向内容
注:不涉及指针,空间分配等资源问题时,深浅拷贝无区别
- 浅拷贝存在的问题:
当类中存在指针或者动态的内存分配时,使用浅拷贝只会将那块内存的位置告知当前对象,并不会重新为新对象分配内存。当程序运行结束后,两个对象分别析构,此时这同一块内存将被释放两次。释放第二次时,析构对象找不到需要释放的内存,就会导致内存泄漏。
实例:
#include <iostream>
using namespace std;
class Demo{
public:
Demo(int n){
p = new int(n);
}
~Demo(){
cout << p << endl;
delete p;
p = NULL;
}
private:
int* p;
};
int main(){
Demo a(10);
Demo b = a;
}
0x13c6e70
0x13c6e70
free(): double free detected in tcache 2
Aborted (core dumped)
问题分析:运行结果可以看出,对象a
的指针p
和对象b
的指针p
指向同一块内存,程序结果时,同一内存被delete
两次导致程序运行出错。
- 解决方式:
深拷贝,重新申请空间,让拷贝对象指向新空间
#include <iostream>
using namespace std;
class Demo{
public:
Demo(int n){
cout << "constructor" << endl;
p = new int(n);
}
Demo(const Demo& d){
cout << "deep copy" << endl;
//创造新的空间,再进行copy操作
p = new int;
*p = *d.p;
}
~Demo(){
cout << p << endl;
delete p;
p = NULL;
}
int GetData(){
return *p;
}
private:
int* p;
};
int main(){
Demo a(10);
Demo b = a;
cout << a.GetData() << "\t" << b.GetData() << endl;
}
constructor
deep copy
10 10
0x23462a0
0x2346280
- 最佳实践:
三大定律(Rule of three / the Law of The Big Three / The Big Three)
如果类中明确定义下列其中一个成员函数,那么必须连同其他二个成员函数编写至类内,即下列三个成员函数缺一不可:
1、析构函数(destructor)
2、复制构造函数(copy constructor)
3、复制赋值运算符(copy assignment operator)
2.7 友元
- 作用
非成员函数访问类中的私有成员
- 分类
全局友元函数:将全局函数声明成友元函数
友元成员函数:类的提前引用声明,将一个函数声明为多个类的友元函数
友元类:将整个类声明为友元
- 特点
友元关系单向性
友元关系不可传递
- 实例:
#include <iostream>
using namespace std;
class Integer {
//友元类
friend class Friend;
//友元函数,允许访问类的私有成员
friend void Func(Integer& num);
private:
int n;
public:
Integer(int n):n(n) {}
void Print() {
cout << n << endl;
}
};
class Friend {
public:
void Func(Integer& num) {
cout << num.n << endl;
}
};
void Func(Integer& num) {
cout << num.n << endl;
}
int main() {
Integer num(10);
num.Print();
Func(num);
Friend f;
f.Func(num);
}
2.8 const
限定符
2.8.1 本质
只读(read only
)
2.8.2 const
与变量/对象
const 类型 变量 = 初始值;
const 类型 对象;
例如:
const int size = 4;
现在比较前卫写法:
类型 const 变量 = 初始值;
类型 const 对象;
例如:
int const size = 4;
- 定义时必须初始化
- 全局作用域声明的
const
变量默认作用域是定义所在文件
-
const
类型参数可以接受const
变量和非const
变量
非const
类型参数只能接受非const
变量
例如:const
对象只能调用const
成员函数
-
const
与宏定义#define
的区别
区别 |
const |
宏定义#define
|
编译器处理方式 |
编译运行阶段使用 |
预处理阶段展开/替换 |
类型 |
有具体的类型 |
没有类型 |
安全检查 |
编译阶段会执行类型检查 |
不做任何类型检查 |
存储方式 |
分配内存 |
不分配内存 |
2.8.3 const
与指针
类型 |
语法 |
作用 |
const 指针 |
类型* const 变量 = 初始值; |
指针指向地址不能改变 |
指向const 对象的指针 |
const 类型* 变量 = 初始值; 类型 const* 变量 = 初始值; |
指针指向对象不能改变 |
指向const 对象的const 指针 |
const 类型* const 变量 = 初始值; |
指针指向地址和对象不能改变 |
指向const
对象的指针是使用最频繁的方式。
2.8.3 const
与引用
类型 const &变量 = 初始值;
与const 类型& 变量 = 初始值;
都是引用对象不能改变。
2.8.5 const
与函数的参数和返回值
类型 |
语法 |
作用 |
说明 |
const 参数 |
返回值类型 函数(const 类型 形参) |
函数内部不能改变参数的值 |
这样的参数的输入值 |
const 返回值 |
const 返回值类型 函数(形参列表) |
函数的返回值不能改变 |
常用于字符串/指针 |
-
const
成员变量
不能在类声明中初始化const
数据成员(C++11可以)
const
成员变量只能在类构造函数的初始化列表中初始化
class 类名{
public:
类名(类型 形参):成员变量(形参){}
private:
const 类型 成员变量;
}
应用:
const
成员变量一般用于类定义后不可修改的信息,例如:学生学号。
-
const
成员函数
成员函数不能修改类中任何成员变量。一般写在成员函数的最后来修饰。
声明:
class 类名{
public:
返回值类型 函数名(形参列表)const;
}
定义:
返回值类型 函数名(形参列表)const;
注:必须在成员函数的声明和定义后都加上const
const 修饰位置 |
作用 |
变量 |
变量不可修改,通常用来替代#define
|
对象/实例 |
对象的成员变量不可修改,只能调用const 成员函数 |
函数参数 |
参数不能在函数内部修改,只作为入参 |
函数返回值 |
返回的结果不能被修改,常用于字符串 |
成员变量 |
只能在初始化列表中初始化 |
成员函数 |
不改变成员变量 |
注:只要能够使用const
,尽量使用const
#include <iostream>
using namespace std;
class Test {
public:
Test():n(100) {
// n = 100 //初始化
}
//如果成员函数不修改(不想修改、不能修改)成语变量,最好在函数后加const
void Print()const {
cout << n << endl;
}
int GetNum() {
return n;
}
private:
const int n;
};
//const 类型参数可以接受const变量和非const变量
//非const类型参数只能接受非const变量
void Print(const char* s) {
cout << s << endl;
}
void Print(const int& n) {
cout << n << endl;
}
int main() {
//const int n = 10;
int const n = 10;
//n = 30; //不允许修改
//const int m; //cosnt定义时必须初始化
//常量指针 指针指向的值不能修改
const int* p1;
int const* p2;
//指针常量 指针指向地址不能修改
//int* const p3;
const int& f1 = n;
int const& f2 = n;
//int& const f3;
char* s = "abc";
Print(s);
const char* s1 = "ABC";
Print(s1);
int m = 10;
Print(m);
Print(n);
Print(20);
const Test t;
t.Print();
// cout << t.GetNum() << endl; //const对象只能调用const成员函数
Test t1;
t1.Print(); //Test::Print(Test* this); // Test::Print(&t1);
cout << t1.GetNum() << endl; //const类型参数可以接受const型和非const型
}
abc
ABC
10
10
20
100
100
100
2.9 static
限定符
- 生存周期:
整个程序的生存周期。
- 作用域:
属于类,不属于对象。
- 语法:
声明:
class 类名{
static 返回类型 函数(形参列表);
};
定义:
返回类型 类名::函数(形参列表){
函数体;
}
调用:
1、通过类名(Class Name)调用
类名::函数(实参列表);
2、通过对象(Object)调用
对象.函数(实参列表);
- 规则
1、static
只能用于类的声明中,定义不能标示为static
。
2、非静态是可以访问静态的成员和函数
3、静态成员函数不能访问普通成员
4、静态成员函数可以设置private,public,protected
访问权限
- 禁忌
1、静态成员函数不能访问非静态(非static
)函数或者变量
2、静态成员函数不能使用this
关键字
3、静态成员函数不能使用cv
限定符(const与volatile
)
因为静态成员函数是属于类而不是某个对象。
volatile
是一个不常用的关键字,作用是改善编译器的优化能力。
- 静态成员变量
语法:
1、在类定义中声明,但是在类实现中初始化。
2、在声明时需要指定关键字static
,但是在类外定义时不要指定static
。
3、对象的大小不包含静态成员变量
因为静态成员变量是属于类而不是某个对象。静态成员变量所有类的对象/实例共享。
static 修饰位置 |
作用 |
变量 |
静态变量 |
函数 |
只源文件内部使用的函数 |
成员变量 |
对象共享变量 |
成员函数 |
类提供的函数,或者作为静态成员对象的接口 |
单例模式:使用静态成员变量和静态成员函数。
#include <iostream>
using namespace std;
class Test{
public:
void Hello();
static void World();
private:
int n;
};
void Test::Hello(){
cout << "Hello\t" << "n:" << n << endl;
}
//函数声明中加static,但是函数实现不能添加static
/* static */void Test::World() /* const */{ //静态成员函数不能添加const是因为函数本身没有传指针(const Test* this)
cout << "World\t" << /*"n:" << n */<< endl; //不能访问普通成员
}
int main(){
Test t;
t.Hello(); //类似Hello(&t);
//Test::Hello(); //错误
//通过类名调用
Test::World(); //类似World()
//对象调用
t.World(); //World()
}
Hello n:0
World
World
实例2:
#include <iostream>
using namespace std;
class Test{
public:
void Hello()const;
static void World();
//private:
static int n; //static属于类,不属于对象
};
//static只能用于类的声明中,定义不能标示为static。
int Test::n = 0; //类外初始化
void Test::Hello()const{ //类似void Test::Hello(const Test* this);
cout << "Hello\t" << "n:" << n << endl;
}
//函数声明中加static,但是函数实现不能添加static
/* static */void Test::World() /* const */{ //不能添加const是因为函数本身没有传指针(const Test* this)
cout << "World\t" << "n:" << n << endl; //不能访问普通(非static)成员
}
int main(){
Test t;
t.Hello(); //类似Hello(&t);
//2种方法
//通过类名调用
Test::World(); //类似World()
//对象调用
t.World(); //World()
cout << sizeof(t) << endl;
Test s;
s.n = 20;
s.World();
}
Hello n:0
World n:0
World n:0
1
World n:20
- 实例3:static属于类,不属于对象
下面代码输出为()
#include <bits/stdc++.h>
using namespace std;
class Test{
public:
static char x;
};
char Test::x='a';
int main(){
Test exp1,exp2;
cout<<exp1.x<<" ";
exp1.x+=5;
cout<<exp2.x <<endl;
}
A. f a
B. a f
C. a a
D. f f
a f
#include <iostream>
using namespace std;
class Object{
public:
Object():id(count++){
++live;
}
~Object(){
--live;
}
static int GetCount(){
return count;
}
static int GetLive(){
return live;
}
void Print()const {
cout << id << ":" << this << endl;
}
private:
const int id; //注意int和const int时候构造函数的区别
static int count;
static int live;
};
int Object::count = 0;
int Object::live = 0;
int main(){
Object o1;
Object o2;
Object o3;
Object o4;
Object(); //匿名对象,构造完即析构
Object();
Object();
Object();
o1.Print();
o2.Print();
o3.Print();
o4.Print();
cout << Object::GetCount() << endl; //累计构造8个对象
cout << Object::GetLive() << endl; //析构4个,还剩4个
cout << sizeof(o1) << endl;
cout << sizeof(Object) << endl;
}
0:0x7ffe1b7759fc
1:0x7ffe1b7759f8
2:0x7ffe1b7759f4
3:0x7ffe1b7759f0
8
4
4
4
2.10 const static
限定符
1、static const
与const static
修饰变量的效果一样
2、属于类,不属于对象
3、不能在构造函数中初始化
实例:
#include <iostream>
using namespace std;
class StaticConstTest{
public:
void print(){
cout << test1 << " " << test2 << endl;
}
private:
static const int test1 = 1;
static const int test2;
};
/* static */ const int StaticConstTest::test2 = 2;
int main(){
StaticConstTest sct;
sct.print();
}
-
cosnt、static、const static
对比
变量类型 |
声明位置 |
一般成员变量 |
在构造函数初始化列表中初始化 |
const 成员常量 |
必须在构造函数初始化列表中初始化 |
static 成员变量 |
必须在类外初始化 |
static const/const static 成员变量 |
变量声明处或者类外初始化 |
注:static const/const static
成员变量在类初始化必须是数字类型
2.11 内联函数
inline
– 宏定义的接班人
- 条件
一般用在代码比较简单的函数
- 语法
1、关键字inline
必须与函数实现/定义体放在一起才能使函数成为内联,将inline
放在函数声明前面不起任何作用
2、定义在类声明之中的成员函数将自动地成为内联函数
3、通常内联函数定义在头文件中。
- 慎用内联
1、如果函数体内的代码比较长,使用内联将导致内存消耗代价较高
2、如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大
3、不要随便地将构造函数和析构函数的定义体放在类声明中
- 本质
内联函数的代码直接替换函数调用,省去函数调用的开销
2.12 运算符重载
1、成员函数运算符重载
返回值类型 operator 运算符(参数){
函数体
}
2、友元函数运算符重载
friend 返回值类型 operator 运算符(形参列表) {
函数体
}
No. |
类型 |
运算符 |
成员函数 |
友元函数 |
1 |
双目算术运算符 |
+ - * / % |
类名 operator 运算符(const 类名&) const |
类名 operator 运算符(const 类名&, const 类名&) |
2 |
关系运算符 |
== != > >= < <= |
bool operator 运算符 (const 类名& ) const |
bool operator 运算符 (const 类名&,const 类名&) |
3 |
双目逻辑运算符 |
&& ¦¦ |
bool operator 运算符 (const 类名& ) const |
bool operator 运算符 (const 类名&,const 类名&) |
4 |
单目逻辑运算符 |
! |
bool operator !() const |
bool operator ! (const 类名&) |
5 |
单目算术运算符 |
+ - |
类名 operator 运算符 () |
类名 operator 运算符 (const 类名&) |
6 |
双目位运算符 |
& ¦ |
类名 operator 运算符 (const 类名& ) const |
类名 operator 运算符 (const 类名& ,const 类名& ) |
7 |
单目位运算符 |
~ |
类名 operator ~ () |
类名 operator ~ (类名&) |
8 |
位移运算符 |
<< >> |
类名 operator 运算符 (int i) const |
类名 operator 运算符 (const 类名&,int i) |
9 |
前缀自增减运算符 |
++ – |
类名 operator 操作符 () |
类名 operator 操作符 (类名&) |
10 |
后缀自增减运算符 |
++ – |
类名 operator ++ (int) |
类名 operator ++ (类名&,int) |
11 |
复合赋值运算符 |
+= -= *= /= %= &= ¦= ^= |
类名& operator 运算符 (const 类名& ) |
类名& operator += (类名&,const 类名&) |
12 |
内存运算符 |
new delete |
参见说明 |
参见说明 |
13 |
流运算符 |
>> << |
- |
参见说明 |
14 |
类型转换符 |
数据类型 |
参见说明 |
- |
15 |
其他运算符重载 |
= [] () -> |
参见说明 |
- |
#include <iostream>
#include <cstring>
using namespace std;
class String {
public:
//构造函数
String(const char* str) {
if(NULL == str) {
this->str = NULL;
size = 0;
} else {
size = strlen(str)+1;
this->str = new char[size];
if(NULL == this->str) return;
strcpy(this->str,str);
}
}
//拷贝构造函数
String(const String& s) {
if(NULL == s.str) {
this->str = NULL;
size = 0;
} else {
size = s.size;
str = new char[size];
if(NULL == str) return;
strcpy(str,s.str);
}
}
//赋值运算符重载
String& operator=(const String& s) {
if(this == &s) return *this; //自我赋值判断
if(NULL == s.str) {
delete [] str;
str = NULL;
size = 0;
} else {
size = s.size;
char* tmp = new char[size];
if(NULL == tmp) return *this;
//释放原有内存
delete [] str;
str = tmp;
strcpy(str,s.str);
}
return *this;
}
String operator+(const String& s)const {
char str[size+s.size+1] = {0};
strcpy(str,this->str);
strcat(str,s.str);
return String(str);
}
bool operator==(const String& s)const {
return strcmp(str,s.str) == 0;
}
bool operator!=(const String& s)const {
//return strcmp(str,s.str) != 0;
return !(*this == s); //调用上面定义的==判断两个对象是否相等
}
friend ostream& operator<<(ostream& os,const String& s);
friend istream& operator>>(istream& is,const String& s);
//析构函数
~String() {
delete [] str;
str = NULL;
size = 0;
}
size_t GetSize() {
return size;
}
char operator[](int i)const {
return str[i];
}
char Get(int i)const {
return str[i];
}
void Print() {
cout << str << endl;
}
private:
char* str;
size_t size;
};
ostream& operator<<(ostream& os,const String& s) {
return os << s.str;
//等价
//os << s.str;
//return os;
}
istream& operator>>(istream& is,const String& s) {
// is >> s.str;
return is;
}
int main() {
String s("abc"); //调用构造
s.Print();
String t = s; //初始化创建对象(从无到有)调用拷贝构造
t = s; //赋值 两个对象都存在 调用赋值运算符重载
t.Print();
t = t; //自我赋值
s.Print();
String n(NULL);
String m = n;
m = n;
m.operator=(n); //等价m = n;
const char* str = "def";
String w = s+s+str+"123";
w.Print();
for(int i = 0; i < s.GetSize(); ++i) {
//cout << s[i]; //s[i]=>s.operator[](i)
cout << s.Get(i) << " ";
}
cout << endl;
cout << w << endl;
cout << (s == w) << "\t" << (s != w) << endl;
String w2 = s+s+str+"123";
cout << (w == w2) << "\t" << (w != w2) << endl;
}
abc
abc
abc
abcabcdef123
a b c
abcabcdef123
0 1
1 0
#include <iostream>
using namespace std;
class Float {
public:
Float(float value):value(value) {}
/*
Float operator+(const Float& f) const {
return Float(this->value + f.value);
}
*/
void Print() {
cout << this->value << endl;
}
/*
//成员函数写法
bool operator==(const Float& f) const {
return abs(this->value - f.value) < 0.000001;
}
bool operator!=(const Float& f) const {
return !abs(*this == f); //使用等于运算符重载函数判断
}
*/
friend Float operator+(const Float& a,const Float& b);
friend bool operator==(const Float& a,const Float& b);
friend bool operator!=(const Float& a,const Float& b);
friend istream& operator>>(istream& is,Float& f);
friend ostream& operator<<(ostream& os,Float& f);
private:
float value;
};
//友元函数写法
bool operator==(const Float& a,const Float& b) {
return abs(a.value - b.value) < 0.000001;
}
bool operator!=(const Float& a,const Float& b) {
return !abs(a == b); //使用等于运算符重载函数判断
}
ostream& operator<<(ostream& os,Float& f) {
os << f.value;
return os;
}
istream& operator>>(istream& is,Float& f) {
is >> f.value;
return is;
}
Float operator+(const Float& a,const Float& b) {
return Float(a.value + b.value);
}
int main() {
Float f1(3.14);
//Float f1 = 3.14; // 等价上面,过程类似于:Float f1 = Float(3.14);
f1.Print();
//成员函数f3 = f1.operator+(1);
//友元函数f3 = operator+(f1,1);
Float f2(1.3);
// Float f3 = f1 + 1.3; // 成员函数可以用,友元函数可以用
Float f3 = 1.3 + f1; // 成员函数不可以用,友元函数可以用
f3.Print();
Float f4(3.14);
cout << (f1 == f4) << endl;
cout << (f3 == f4) << endl;
cout << (3.14 == f4) << endl;
cout << (f4 == 3.14) << endl;
//f1.operator==(f2); // 成员函数判断
//operator==(f1,f2); // 友元函数判断
cout << f1 << " " << f2 << endl;
//等价过程:
//operator<<(cout,f1);
//cout << " ";
//operator<<(cout,f2);
//cout << endl;
cin >> f1;
f1.Print();
}
3.14
4.44
1
0
1
1
3.14 1.3
3.1415
3.1415