C++面向对象编程

C++面向对象编程

面向对象基础

实体(属性,行为) ADT(abstract data type)

面向对象语言的四大特征:抽象,封装(隐藏),继承,多态

访问限定符:public 共有的,private 私有的, protected 保护的

类的成员函数和内联函数inline的区别?

一个类的成员方法,在类内声明和定义,那么它本身就是inline函数。如果类的成员方法在类内声明,类外定义,且没有关键字inline修饰,那么该方法就是类的普通的函数。如果在类外定义类的成员方法,并且用inline关键字修饰为内联函数,则该函数必须写在和声名在一个头文件中,,否则编译就会出现问题。具体查看inline 成员函数。

有无inline修饰的区别在于:普通函数调用需要先开辟形参变量内存空间,在运行函数主体,结束时再回收空间。而inline修饰后可以类似于将函数体直接转在调用点后面,就节省了函数调用内存空间的开辟和回收。但是inline是否起作用,还需要看编译器是否将其编译为内敛函数,如果太过于复杂的函数体,那么极大可能就不会成为内联函数。具体可以查看C++函数调用那些事。

C++对象内存大小的计算方法:

对象的内存大小只和成员变量有关,和成员方法无关。

可以使用vs中的终端,使用命令cl xxx.cpp /d1eportSingleClassLayoutXXX查看xxx.cppXXX类的内存大小。

#include<iostream>
#include<string>using namespace std;const int LEN = 20;
class TestObj
{
public:void setX(int n);void setY(int n);void setName(const char* name);
private:int x;int y;char name[LEN];
};
int main()
{return 0;
}


C++类对象属性是独立的,也就是各个对象的成员属性是单独的,互不影响。但是类的成员方法是公用的,类对象都可以访问,编译后都放在代码段。

类的成员方法一经过编译,所有的方法参数,都会加一个this指针,接收调用该方法的对象的地址。

类的构造函数和析构函数

没有提供任何构造函数的时候,编译器会自动生成默认构造和默认析构,是空函数(函数里面没有函数体部分)。自己定义了构造函数后,编译器就不会在创建默认构造函数。

对象析构的顺序和创建的顺序相反:后构造的先析构

//构造函数和析构函数,实现一个顺序栈
//构造函数和析构函数和类名一样,没有返回值

#include<iostream>using namespace std;//构造函数和析构函数,实现一个顺序栈
//构造函数和析构函数和类名一样,没有返回值
class SeqStack
{
public:void init(int size = 10){_pstack = new int[size];_top = -1;_size = size;}SeqStack(int size=10) //是可以带参数的,因此可以提供多个构造函数{	cout << this << "SeqStack" << endl;_pstack = new int[size];_top = -1;_size = size;}~SeqStack() //没有参数的,所以析构函数只有一个,析构函数调用以后,对象就不存在了{cout << this << "~SeqStack" << endl;delete[] _pstack;_pstack = nullptr;}void release(){delete[] _pstack;_pstack = nullptr;}void push(int val){if (full()) resize();_pstack[++_top] = val;}void pop(){if (empty()) return;--_top;}int top(){return _pstack[_top];}bool empty() { return _top == -1; }bool full() { return _top == _size - 1; }private:int* _pstack;//动态开辟数组,存储顺序栈的元素int _top;//顶部元素的值int _size;//数组扩容的总大小void resize(){int* ptmp = new int[_size * 2];for (int i = 0; i < _size; i++){ptmp[i] = _pstack[i];}delete[] _pstack;_pstack = ptmp;_size = _size * 2;}
};
int main()
{SeqStack s;//s.init(5); //空间初始化,现在用构造函数代替,初始化操作容易被忘记for (int i = 0; i < 15; i++){s.push(i);}while (!s.empty()){cout << s.top() << " ";s.pop();}cout << endl;//s.release();//堆空间析构,现在用析构函数代替,因为空间释放很容易被忘记SeqStack s2(50);//s2.~SeqStack();//自己调用析构函数释放资源,一般不自己写调用析构函数SeqStack s3=new SeqStack();//malloc内存开辟,在使用构造函数进行对象初始化//必须显式回收s3的内存资源,否则作用域结束后也不会析构,因为new创建的对象存储在堆区delete s3;//先执行析构s3.~SeqStack();在执行free(ps);return 0;
}

对象的深拷贝和浅拷贝

对象的默认拷贝构造函数是做内存的数据拷贝,关键是对象如果占用外部资源(使用堆空间),那么浅拷贝就会出现问题。

所以在类创建的对象存在外部资源访问的情况下,需要重新定义类的默认拷贝构造函数和operator=函数,否则可能会出现对象析构时出现释放野指针的情况。

还是以SeqStack为例,主要看拷贝构造函数和operator=函数,以及main函数的注释代码。

#include<iostream>using namespace std;//构造函数和析构函数,实现一个顺序栈
//构造函数和析构函数和类名一样,没有返回值
class SeqStack
{
public:void init(int size = 10){_pstack = new int[size];_top = -1;_size = size;}SeqStack(int size=10){	cout << this << "SeqStack" << endl;_pstack = new int[size];_top = -1;_size = size;}SeqStack(const SeqStack& src){/*//默认的拷贝构造函数长这样_pstack = src._pstack;//指针赋值,也就是将新的对象的_pstack也指向原来的src._pstack的地址_top = src._top;_size = src._size;*/cout << "SeqStack(const SeqStack&)" << endl;_pstack = new int[src._size];for (int i = 0; i < src._top; i++)//还需要将内存中的值及进行拷贝{_pstack[i] = src._pstack[i];}_top = src._top;_size = src._size;}void operator=(const SeqStack& src){cout << "operator=" << endl;//需要先释放当前对象的内存,防止内存丢失(因为采用=重载,左边的对象一般存在空间)//防止自己给自己赋值if (this == &src){return;}delete[]_pstack;_pstack = new int[src._size];for (int i = 0; i < src._top; i++)//还需要将内存中的值及进行拷贝{_pstack[i] = src._pstack[i];}_top = src._top;_size = src._size;}~SeqStack(){cout << this << "~SeqStack" << endl;delete[] _pstack;_pstack = nullptr;}void release(){delete[] _pstack;_pstack = nullptr;}void push(int val){if (full()) resize();_pstack[++_top] = val;}void pop(){if (empty()) return;--_top;}int top(){return _pstack[_top];}bool empty() { return _top == -1; }bool full() { return _top == _size - 1; }private:int* _pstack;//动态开辟数组,存储顺序栈的元素int _top;//顶部元素的值int _size;//数组扩容的总大小void resize(){int* ptmp = new int[_size * 2];for (int i = 0; i < _size; i++){ptmp[i] = _pstack[i];}delete[] _pstack;_pstack = ptmp;_size = _size * 2;}
};
int main()
{SeqStack s1(10);/*调用默认拷贝构造函数进行对象s2的实例化,这里会存在一个严重的问题,因为SeqStack会开辟一个堆空间,调用默认拷贝构造函数会导致s2和s1共用一个堆内存空间,在s2析构完后会将空间释放,而s1在释放该内存就会出现内存访问异常*///SeqStack s2 = s1;//这里使用默认拷贝构造会导致问题,具体查看拷贝构造函数//SeqStack s3(s1);//其实和上面SeqStack s2 = s1;一样,也是调用拷贝构造函数SeqStack s2 = s1;//s2.operator=()s2 = s1;//如果对象没有重写=操作符函数,那么默认就是做浅拷贝,内存数据的拷贝,类似默认拷贝构造函数//而且这里会存在一个内存泄漏的严重问题,将s1._pstack -> s2._pstack导致原来的s2._pstack没有释放就被丢弃了//这里也会出现内存访问异常,想再次释放空间的错误,释放野指针return 0;
}

拷贝构造函数和赋值操作符重载实践

自定义实现String

#include<iostream>
#include<string>
using namespace std;class String
{
public:String(const char* str = nullptr){if (str != nullptr){m_data = new char[strlen(str) + 1];strcpy(m_data, str);}else{m_data = new char[1];m_data[0] = '\0';}}String(const String& other){m_data = new char[strlen(other.m_data) + 1];strcpy(m_data, other.m_data);}~String(){delete[]m_data;m_data = nullptr;}//String& 是为了实现多个后续情况 str1=str2=str3;String& operator=(const String& other){if (this == &other){return *this;}delete[]m_data;m_data = new char[strlen(other.m_data) + 1];strcpy(m_data, other.m_data);return *this;}private:char* m_data;
};
int main()
{//调用String(const char* str = nullptr)构造函数String str1;String str2("hello");String str3 = "hello";//调用拷贝构造函数String str4(str3);String str5 = str3;//赋值操作符重载str1 = str2;return 0;
}

自定义实现循环Queue

#include <iostream>
using namespace std;class Queue
{
public:Queue(int size = 10){_pQue = new int[size];_front = _rear = 0;_size = size;}Queue(const Queue& src){_front = src._front;_rear = src._rear;_size = src._size;_pQue = new int[_size];for (int i = src._front; i != _rear;i=(i+1)%_size){_pQue[i] = src._pQue[i];}}Queue& operator=(const Queue&src){if(this==&src){return *this;}delete[] _pQue;_front = src._front;_rear = src._rear;_size = src._size;_pQue = new int[_size];for (int i = src._front; i != _rear; i = (i + 1) % _size){_pQue[i] = src._pQue[i];}}~Queue(){delete[] _pQue;_pQue = nullptr;}void push(int val){if (full())resize();_pQue[_rear++] = val;_rear = _rear % _size;}void pop(){if (empty())return;_front = (_front + 1) % _size;}int front(){return _pQue[_front];}bool full(){return (_rear + 1) % _size == _front;}bool empty(){return _rear == _front;}private:int *_pQue; // 申请队列的空间数组int _front; // 指示队头的位置int _rear;	// 指示队尾的位置int _size;void resize(){int *temp = new int[_size * 2];int index = 0;for (int i = _front; i != _rear; i = (i + 1) % _size){temp[index++] = _pQue[i];}delete[] _pQue;_pQue = temp;_front = 0;_rear = index;_size = 2 * _size;}
};
int main()
{Queue q;for (int i = 0; i < 30; i++){q.push(i);}while (!q.empty()){cout << q.front() << " ";q.pop();}Queue m = q;m = q;return 0;
}

构造函数的初始化列表

使用类的对象作为一个类的成员变量,这里举了一个商品和事件的示例:主要看Goods类构造函数的解析

class Goods
{
public:Goods(const char* n, int a, int p, int y, int m, int d):_data(y, m, d) //Data _data(y,m,d); 这里会调用Data的构造函数,也称为构造函数的初始化列表 #step1先执行//,_amount(a) //int _amount=a;{// #step2 后执行strcpy(_name, n);_amount = a; //int _amount;_amount=a;_price = p;}void show(){cout << "name: " << _name << endl;cout << "amount: " << _amount << endl;cout << "price: " << _price << endl;_data.show();}
private:char _name[20];int _amount;double _price;Data _data;//如果没有默认构造函数就会初始化错误,没有构造函数可用
};
int main()
{Goods g("面包", 100, 25.0, 2023, 8, 1);g.show();Test t;t.show();return 0;
}

成员变量的初始化顺序和它们定义的顺序有关,和构造函数的初始化列表中出现的先后顺序无关

class Test
{
public:Test(int data = 10) :mb(data), ma(mb) {};//这里初始化的顺序按照定义的顺序,ma先初始化,mb后初始化void show() { cout << "ma: " << ma << "mb: " << mb << endl; } //ma: -858993460mb: 10
private://成员变量的初始化顺序和它们定义的顺序有关,和构造函数的初始化列表中出现的先后顺序无关。int ma;int mb;
};

类的各种成员

类的各种成员方法,包括类的成员方法和类的成员变量。

普通成员变量和普通成员方法都依赖于对象,适用对象进行访问和调用(这是因为普通成员方法都会产生一个this指针)。

静态成员变量和方法,都没有this指针,不需要依赖对象调用。类中有静态成员变量时,必须要类内声明,类外定义(如果不赋初值,则默认就是0)。静态成员变量的一些用途有:可以在每次调用构造函数的时候,让静态成员变量+1,相当于可以计数使用。静态成员方法的一些用途:可以定义静态成员方法,直接使用类名+静态成员方法名访问静态成员变量,获得计数的个数。

普通成员方法: 编译器会添加一个this形参变量

  1. 属于类的作用域。
  2. 调用该方法时,需要依赖一个对象。
  3. 可以任意访问对象的私有成员,protected 继承 public private

static静态成员方法:编译器不会添加this形参变量

  1. 属于类的作用域。
  2. 可以使用类名作用域来调用方法。
  3. 可以任意访问对象的私有成员,仅限于不依赖对象的成员(只能调用其他的static静态成员

常成员方法:

  1. 属于类的作用域。
  2. 调用该方法时,需要依赖一个普通对象(类型访问)或者常对象,如下代码所示。
  3. 可以任意访问对象的私有成员,只能读,不能写。
  4. 只要是只读操作的成员方法,最好实现为常成员方法,防止错误。
class Test
{
public:void func() { cout << "call Test::func" << endl; }static void static_func() { cout << "call Test::static_func" << endl;cout << mb << endl;// cout << ma << endl; //错误,因为调用了非静态成员变量,静态成员函数不会产生this指针}void show() //Test *this{cout<<ma<<endl;}void show()const //const Test *this,构成函数重载,一般只读的方法设置为常成员方法{cout<<ma<<endl;}int ma;static int mb;//静态成员方法必须在类内声明,类外定义,初始化
};
int Test::mb = 30;int main()
{const Test t();t.show();//不能调用show()方法,因为是调用的时候传入的是const Test *this,//而普通的show方法的参数是Test *this,不能将Test *this <- const Test *this转化//所以需要添加一个void show() const{}方法,也成为常方法,和void show()构成函数重载//加了const的show方法,传入的this指针式const的指针,所以参数列表不一样
}

指向类成员的指针

指向类成员(成员变量和成员方法)的指针。

指向成员变量的指针:

//指向类成员(成员变量和成员方法)的指针
class Test
{
public:void func() { cout << "call Test::func" << endl; }static void static_func() { cout << "call Test::static_func" << endl; }int ma;static int mb;//静态成员方法必须在类内声明,类外定义,初始化,不录入对象内存中
};
int Test::mb = 30;//如果不赋值的话那么编译器会默认给0int main()
{	//int a = 10; int* p = &a; *p = 30; //可以通过普通的指针变量修改值//int Test::*p = &Test::ma; //但是对于普通成员而言,没有对象直接谈论其成员变量是没有意义的Test t1;Test* t2 = new Test();int Test::*p = &Test::ma;t1.*p = 20;cout << t1.*p << endl;t2->*p = 30;cout << t2->*p << endl;int* p1 = &Test::mb;//静态成员不依赖于类*p1 = 40;cout << *p1 << endl;delete t2;//释放空间return 0;
}

指向类的成员方法指针:

//指向类成员(成员变量和成员方法)的指针
class Test
{
public:void func() { cout << "call Test::func" << endl; }static void static_func() { cout << "call Test::static_func" << endl; }int ma;static int mb;//静态成员方法必须在类内声明,类外定义,初始化
};
int Test::mb = 30;int main()
{	Test t1;Test* t2 = new Test();//C语言定义的函数指针严重性,无法从“void(__thiscall Test::*)(void)”转换为“void(__cdecl*)(void)//void(*pfunc)() = &Test::func;void(Test:: * pfunc)() = &Test::func;(t1.*pfunc)();(t2->*pfunc)();//static修饰的成员方法不依赖于对象,其地址就是普通地址,不需要加类作用域void(* psfunc)() = &Test::static_func;(*psfunc)();//静态方法不依赖于对象调用delete t2;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/69037.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解锁编程的新契机:深入探讨Kotlin Symbol Processor (KSP)的编写

解锁编程的新契机&#xff1a;深入探讨Kotlin Symbol Processor (KSP)的编写 1. 引言 随着软件开发领域的不断发展&#xff0c;新的工具和技术不断涌现&#xff0c;以满足开发者在构建高效、可维护和创新性的代码方面的需求。Kotlin Symbol Processor&#xff08;KSP&#xf…

前后端分离------后端创建笔记(09)密码加密网络安全

本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论&#xff0c;如有侵权请联系 源码&#xff1a;https://gitee.com/green_vegetables/x-admin-project.git 素材&#xff1a;https://pan.baidu.com/s/…

使用maven打包时如何跳过test,有三种方式

方式一 针对spring项目&#xff1a; <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>true</skipTests> </configuration> …

问AI一个严肃的问题

chatgpt的问世再一次掀起了AI的浪潮&#xff0c;其实我一直在想&#xff0c;AI和人类的关系未来会怎样发展&#xff0c;我们未来会怎样和AI相处&#xff0c;AI真的会完全取代人类吗&#xff0c;带着这个问题&#xff0c;我问了下chatgpt&#xff0c;看一看它是怎么看待这个问题…

PyQt6安装教程

目录 1、安装PyQt6和pyqt6-tools 2、在Pycharm里配置Qt Designer 3、配置Pyuic工具 4、配置Pyrcc工具 5、三个工具的作用 1、安装PyQt6和pyqt6-tools pip install PyQt6 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install PyQt6-tools -i https://pypi.tuna.tsin…

uniapp----分包

系列文章目录 uniapp-----封装接口 uniapp-----分包 目录 系列文章目录 uniapp-----封装接口 uniapp-----分包 前言 二、使用步骤 1.创建文件 ​编辑 2.min.js的修改 2.1 subPackages 代码如下&#xff08;示例&#xff09;&#xff1a; 2.2 preloadRule 代码如下&am…

redis事务对比Lua脚本区别是什么

redis官方对于lua脚本的解释&#xff1a;Redis使用同一个Lua解释器来执行所有命令&#xff0c;同时&#xff0c;Redis保证以一种原子性的方式来执行脚本&#xff1a;当lua脚本在执行的时候&#xff0c;不会有其他脚本和命令同时执行&#xff0c;这种语义类似于 MULTI/EXEC。从别…

uniapp开发(由浅到深)

文章目录 1. 项目构建1.1 脚手架构建1.2 HBuilderX创建 uni-app项目步骤&#xff1a; 2 . 包依赖2.1 uView2.2 使用uni原生ui插件2.3 uni-modules2.4 vuex使用 3.跨平台兼容3.1 条件编译 4.API 使用4.1 正逆参数传递 5. 接口封装6. 多端打包3.1 微信小程序3.2 打包App3.2.1 自有…

棒球发展史·棒球1号位

棒球发展史 1. 棒球的起源 棒球的起源地棒球的起源地。棒球&#xff0c;也被称为垒球或棒球运动&#xff0c;起源于19世纪晚期的美国。当时在美国&#xff0c;体育运动已经有了较为完备的体制&#xff0c;也形成了多种不同的运动形式。然而&#xff0c;最受欢迎的体育运动主要…

【第三阶段】kotlin语言的split

const val INFO"kotlin,java,c,c#" fun main() {//list自动类型推断成listList<String>val listINFO.split(",")//直接输出list集合&#xff0c;不解构println("直接输出list的集合元素&#xff1a;$list")//类比c有解构&#xff0c;ktoli…

ReactDOM模块react-dom/client没有默认导出报错解决办法

import ReactDOM 模块“"E:/Dpandata/Shbank/rt-pro/node_modules/.pnpm/registry.npmmirror.comtypesreact-dom18.2.7/node_modules/types/react-dom/client"”没有默认导出。 解决办法 只需要在tsconfig.json里面添加配置 "esModuleInterop": true 即…

chatGPT小白快速入门培训课程-001

一、前言 本文是《chatGPT小白快速入门培训课程》的第001篇文章&#xff0c;全部内容采用chatGPT和chatGPT开源平替软件生成。完整内容大纲详见&#xff1a;《chatGPT小白快速入门课程大纲》。 本系列文章&#xff0c;参与&#xff1a; AIGC征文活动 #AIGC技术创作内容征文# …