栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

二、C++语言初阶:类与对象

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

二、C++语言初阶:类与对象

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 
#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 
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 
#include 
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 << "件ttt¥" << sum << endl;
}
2.2.2 C++class与struct区别
  • 1、默认的访问控制不同
    struct是public,class默认是private
  • 2、struct可以使用花括号内的初始值列表{...}初始化,class不可以(C++98不可以,C++11可以)。
    注:
    (1)C++的struct可以有成员函数,而C不可以。
    (2)C++的struct可以使用访问控制关键字(public private protected),而C不可以。
    (3)C++的struct成员变量默认初始化为随机值(主要影响指针)。
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(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、只能有一个析构函数

  • 调用时机
    对象离开作用域
    delete

  • 默认析构函数
    类中没有显式的定义析构函数,编译器就会自动为该类型生成默认析构函数

  • 作用
    释放对象所申请占有的资源

  • 实例:

#include 
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 s(10);
}

2.4.3 引用
  • 语法:
const 类型名& 对象名/类型名& 对象名
  • 使用:
    与对象变量、基本类型变量一样
  • 实例1:
#include 
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 
#include 
#include 
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 
#include 
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(); //真实执行拷贝构造但是被编译器优化了
    
    //cout << "&t:" << &t << endl;
}
}
2.4.5 默认拷贝构造函数
  • 本质:
    内存拷贝
  • 作用:
    复制一个已经存在的对象
  • 实例1:
#include 
#include 

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)
  • 实例2:
#include 
#include 
// 禁用编译优化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 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 
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; // 禁用赋值运算符重载的方法

//禁用拷贝构造函数和赋值运算符重载的方法二:加入私有

};
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 
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 
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 
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 
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 
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修饰位置作用
变量静态变量
函数只源文件内部使用的函数
成员变量对象共享变量
成员函数类提供的函数,或者作为静态成员对象的接口

单例模式:使用静态成员变量和静态成员函数。

  • 实例1:
#include 
using namespace std;
class Test{
public:
    void Hello();
    static void World();
private:
    int n;
};
void Test::Hello(){
    cout << "Hellot" << "n:" << n << endl;
}
//函数声明中加static,但是函数实现不能添加static
void Test::World() {  //静态成员函数不能添加const是因为函数本身没有传指针(const Test* this)
    cout << "Worldt" << << endl; //不能访问普通成员
}
int main(){
    Test t;
    t.Hello(); //类似Hello(&t);
    //Test::Hello(); //错误
    //通过类名调用
    Test::World(); //类似World()
    //对象调用
    t.World(); //World()
}
Hello	n:0
World	
World	

实例2:

#include 
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 << "Hellot" << "n:" << n << endl;
}
//函数声明中加static,但是函数实现不能添加static
void Test::World() {  //不能添加const是因为函数本身没有传指针(const Test* this)
    cout << "Worldt" << "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 
using namespace std;
class Test{
public:
    static char x;
};
char Test::x='a';
int main(){
    Test exp1,exp2;
    cout< 

A. f a
B. a f
C. a a
D. f f

  • 答案:
a f
  • 实例4:
#include 
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 
using namespace std;
class StaticConstTest{
public:
    void print(){
        cout << test1 << " " << test2 << endl;
    }
private:
    static const int test1 = 1;
    static const int test2;
};

 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 类名& ) constbool operator 运算符 (const 类名&,const 类名&)
3双目逻辑运算符&& ¦¦bool operator 运算符 (const 类名& ) constbool operator 运算符 (const 类名&,const 类名&)
4单目逻辑运算符!bool operator !() constbool 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其他运算符重载= [] () ->参见说明-
  • 实例1:
#include 
#include 
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
  • 实例2:
#include 
using namespace std;

class Float {
public:
    Float(float value):value(value) {}
    
    void Print() {
        cout << this->value << endl;
    }
    
    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
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/835637.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号