C++(this指针/常函数与常对象/拷贝构造函数/赋值函数/静态成员/静态成员函数/单列模式)

一、this指针与常函数

成员函数是如何区别调用它的对象?
#include <iostream>
using namespace std;
​
class Test
{const int num;
public:Test(int num):num(num) {}void show(void){cout << num << " " << &num << endl;}
};
​
int main(int argc,const char* argv[])
{Test t1(1234), t2(5678);t1.show();t2.show();
}
C语言中我们如何解决该问题:

由于C语言中的结构没有成员函数,只能定义普通函数,取函数名时,让它与结构有关联,然后在函数的第一个参数把结构变量的地址传递过来,从而区别每个调用它的结构变量。

在C++语言中该问题由编译器帮忙解决:

1、C++语言中的结构、联合、类可以定义成员函数,成员函数都会有一个隐藏的参数。

2、该参数就是个地址,也叫this指针,可以显式使用。

3、使用结构、联合、类对象可以直接调用成员函数时,编译器会自动计算对象的地址隐式的传递给成员函数。

4、所以在成员函数中可以区分是哪个对象调用了它,并且在成员函数内访问成员变量时,编译器帮我们隐式在每个成员变量前增加了this->,所以可以区别出每个对象的成员变量。

5、由于成员函数参数列表中都隐藏着this指针,所以普通的成员函数无法作为回调函数使用。

#include <iostream>
#include <pthread.h>
#include <signal.h>
using namespace std;
​
class Test
{int num;
public:Test(int num):num(num){ }
​void* run(Test* this,void* arg){   
​}   void sigint(int signum){   
​}   
​void show(void){   cout << num << " " << &num << endl;}   
};
​
int main(int argc,const char* argv[])
{signal(SIGINT,Test::sigint);
​pthread_t tid;pthread_create(&tid,NULL,Test::run,NULL);
​return 0;
}

6、显式使用this指针可以解决函数参数与成员变量同名的问题。

#include <iostream>
#include <pthread.h>
#include <signal.h>
using namespace std;
​
class Test
{int num;
public:Test(int num):num(num){ }void setNum(int num){this->num = num;cout << this->num << " - " << &this->num << endl;}
​void show(void){cout << num << " " << &num << endl;}
};
​
int main(int argc,const char* argv[])
{Test t1(1234), t2(5678);t1.show();t2.show();t1.setNum(6666);t1.show();
}
常对象与常函数
什么是常函数

在成员函数的参数列表的末尾(小括号后面),用const修饰,这种成员就叫常函数。

class 类名
{
public:// 常函数返回值 函数名(参数列表) const{}
};
常函数的特点:

常函数隐藏的this指针具有cosnt属性。

什么是常对象

在定义结构、联合、类对象时,使用const修饰,这种对象就叫常对象。

const 类名 对象名;
const 类名* 指针变量 = new 类名;
常对象的特点:

使用常对象调用成员函数时,编译器计算出的对象地址(this)也具有const属性。

常对象有常函数的局限性:

1、常对象不能调用普通成员函数,只能调用常函数(构造函数和析构函数除外),但普通对象既可以普通成员函数,也可以调用常函数。

2、在常函数中不能显式修改成员变量,并且也不能调用普通的成员函数,只能调用常函数。

3、如果类的对象一定会被const修饰,那么它的成员函数都要定义为常函数。

4、如果类的对象可能被const修饰,也可能不修饰,那么它的成员函数要写两份,一份常函数,另一份普通成员函数。

mutable关键字的作用

如果常函数的const属性与函数的功能发生冲突,一定要修改成员变量,那么使用mutable关键字修饰一下需要在常函数中修改的成员变量。

#include <iostream>
#include <pthread.h>
#include <signal.h>
using namespace std;
​
class Test
{mutable int num;
public:Test(int num):num(num){ }
​void setNum(int num) const{this->num = num;cout << this->num << " - " << &this->num << endl;}
​void show(void) const{cout << num << " " << &num << endl;}
};
​
int main(int argc,const char* argv[])
{const Test t(1234);t.show();t.setNum(23456);t.show();
​return 0;
}
空类的对象为什么占用1字节内存?

1、因为C++中的空的结构、联合、类里面有隐藏的成员函数。

2、成员函数的参数列表中有隐藏this指针,调用这些成员函数时就需要计算出对象的地址传递给成员函数。

3、空的类对象至少要在内存中占据一个字节,才可以计算出this指针,传递成员函数,完成函数调用。

练习:使用C++语言 实现PCM 模型,并结合NetWork 实现基于TCP的回声服务器。

二、拷贝构造函数和赋值函数

什么是拷贝构造

是一种特殊构造函数,如果没有显式的实现,编译器就会自动生成。

class 类名
{
public:// 拷贝构造类名(const 类名& that){}
};
什么时候会调用拷贝构造

当使用一个类对象给另一个新的类对象初始化时,就会自动调用拷贝构造。

#include <iostream>
using namespace std;
​
class Test
{
public:Test(void){   cout << "调用了普通的构造函数" << endl;}   Test(const Test& that){   cout << "调用了拷贝构造" << endl;}   
};
​
void func(Test t)
{
​
}
​
int main(int argc,const char* argv[])
{Test t1;        // 调用的是普通构造Test t2 = t1;   // 调用的是拷贝构造func(t1);       // 调用的是拷贝构造return 0;
}
拷贝构造的任务是什么

拷贝构造参数对象的所有成员变量挨个赋值给新对象的成员变量,一般情况下编译器自动生成的拷贝构造就能完全满足我们使用需求。

什么时候需要显式实现拷贝构造

当成员变量中有指针成员且指向了堆内存,就需要显式实现拷贝构造。

编译器自动生成的拷贝构造,只会对成员变量挨个赋值,如果成员变量中有指针变量且指向堆内存,结果就两个对象的指针变量同时指向一份堆内存,当它们执行析构函数时,会把这块堆内存释放两次,产生 double free or corruption 的错误。

正确的做法应该是先给新对象的指针变量重新申请一份堆内存,然后把旧对象的指针变量所指向的内存拷贝到新对象的指针变量所指向的内存。

#include <iostream>
using namespace std;
​
class Test
{int* ptr;
public:Test(int num){ptr = new int;cout << "new:" << ptr << endl;*ptr = num;}
​~Test(void){cout << "delete:" << ptr << endl;delete ptr;}
​/* 编译器生成的拷贝构造,会造成 double freeTest(const Test& that){ptr = that.ptr; }*/Test(const Test& that){// 给新对象的指针变量重新申请堆内存ptr = new int(*that.ptr);// 把旧对象的指针变量所指向的内存拷贝给新对象的指针变量所指向的内存,如果不方便解引用时可以使用memcpy函数}
​void show(void){cout << "val:" << *ptr << " addr:" << ptr << endl;}
};
​
int main(int argc,const char* argv[])
{Test t1(12345);Test t2 = t1;t1.show();t2.show();
​return 0;
}
什么是赋值函数

是一种特殊的成员函数,如果没有显式实现,编译器会自动生成。

class 类名
{
public:// 赋值函数const 类名& operator=(const 类名& that){}
};
什么时候会调用赋值函数

当一个旧对象给另一个旧对象赋值时会自动调用赋值函数。

当一个旧对象给另一个新对象初始化时会自动调用拷贝构造函数。

#include <iostream>
using namespace std;
​
class Test
{
public:Test(const Test& that){   cout << "调用了拷贝构造" << endl;}   
​void operator=(const Test& that){   cout << "调用了赋值函数" << endl;}   
};
​
int main(int argc,const char* argv[])
{Test t1;        // 调用了普通的构造函数Test t2 = t1;   // 调用了拷贝构造t1 = t2;        // 调用的是赋值函数return 0;
}
赋值函数的任务是什么

赋值函数与拷贝构造的任务几乎相同,都是挨个给成员变量赋值,但如果需要显式实现时,它的业务逻辑不同。

什么时候需要显式实现赋值函数

当需要显式实现拷贝构造时,就需要显式实现赋值函数,它们两个面临问题是一样的。

赋值函数不应该对成员指针变量赋值,而应该对象成员指针变量所指向的内存进行拷贝。

#include <iostream>
using namespace std;
​
class Test
{int* ptr;
public:Test(int num){ptr = new int;cout << "new " << ptr << endl;*ptr = num;}~Test(void){cout << "delete " << ptr << endl;// delete ptr;}
​Test(const Test& that){ptr = new int;// 如果不方便解引用,可以调用memcpy函数进行拷贝*ptr = *that.ptr;cout << "new " << ptr << "调用了拷贝构造" << endl;}
​const Test& operator=(const Test& that){// 当ptr和that.ptr指向的内存块大小一样,可以直接进行内存拷贝*ptr = *that.ptr;cout << "调用了赋值函数" << endl;return *this;/*当对象的ptr指向的内存与与that.ptr指向的内存块不一样大先释放旧的ptr再分配新的,要与that.ptr的内存块一样大然后再拷贝*/}
};
​
int main(int argc,const char* argv[])
{Test t1(1234);      // 调用了普通的构造函数Test t2 = t1;   // 调用了拷贝构造t1 = t2;        // 调用的是赋值函数return 0;
}
浅拷贝与深拷贝

拷贝就是一个对象给另一个对象赋值,编译器自动生成的拷贝构造和赋值函数执行的业务逻辑就是浅拷贝(成员指针给成员指针赋值),深拷贝就是把成员指针所指向的内存拷贝给另一个成员指针所指向的内存。

浅拷贝就是指针给指针赋值,深拷贝就内存给内存赋值。

注意:如果成员变量中没有成员指针,则浅拷贝就可以满足需求,如果如果成员变量中有成员指针且指向堆内存,则必须手动实现深拷贝,否则就会出现 double free or corruption 的错误。

练习:

自定义MyString类,并实现构造、析构、拷贝构造、赋值等函数。

class MyString
{char* cStr;
public:MyString(const char* str=""){cStr = new char[strlen(str)+1];strcpy(cStr,str);}~MyString(void){delete[] cStr;}MyString(const MyString& that){cStr = new char[strlen(that.cStr)+1];strcpy(cStr,that.cStr);}const MyString& operator=(const MyString& that){if(this != &that){delete[] cStr;cStr = new char[strlen(that.cStr)+1];strcpy(cStr,that.cStr);}return *this;}
};

三、静态成员

普通成员
普通成员变量的特点:

每创建一个对象,就分给该对象分配一块内存,里面存储成员变量,每多一个对象就多一份成员变量。

普通成员函数的特点:

成员函数的参数列表中隐藏一个this指针,当通过对象调用成员函数时,编译器会自动计算出对象的地址隐式的传递给this。

只能通过类对象才能调用成员函数。

静态成员
静态成员变量
什么是静态成员变量

被static修饰过的成员变量叫静态成员变量。

class Test
{// 静态成员static int num;
public:void show(void){   cout << num << endl;}   
};
静态成员的特点和局限性:

1、静态成员只能在类内声明,定义和初始化必须放在类外。

2、静态成员使用的是data或bss内存段,所以类中的静态成员只有一份,所有类对象共用这一份静态成员。

3、如果类中有静态成员,计算类对象字节数时,静态成员不包含在内。

静态成员函数
什么是静态成员函数

被static修饰的成员函数叫静态成员函数。

class Test
{static int num;
public:void show(void){   cout << num << " " << &num << endl;}static void func(void){}
};
静态成员函数的特点和局限性:

1、静态成员函数的参数列表中没有隐藏的this指针。

2、静态成员函数中不能直接访问成员变量,也不能调用其它成员函数,但可以访问静态成员也可以调用其它静态成员函数。

3、静态成员函数可以使用 类名::函数名(实参) 调用,不需要通过类对象,虽然也可以通过类对象调用,但依然不能直接访问对象的成员变量。

4、静态成员函数的内部,也算是类内,虽然不能直接访问成员变量,但如果把类对象,作为参数传递给静态成员函数,那么它依然能访问成员变量。

静态成员的作用

1、静态成员变量就相当于把普通全局变量的作用域限制到类内,如果它的访问权限是public,就可以当全局变量使用,只是需要在变量名前面加 类名::静态成员变量。

2、可以把类对象的共用成员设置为静态成员变量,这样可以达到节约内存的目的,也可以作为类的管理信息。

3、静态成员函数相当于把普通函数的作用域限制到类内,当作给所有对象提供了一个统一的管理接口,可以不破坏类的封装性前提下访问静态成员,对类对象进行管理和设置(管理信息就是私有的静态成员变量)。

4、静态成员函数由于没有了隐藏的this指针,就可以作为回调函数使用了。

C语言中 static 与C++的 static 的区别?

C语言中的static有什么功能?

C++的 static 有什么新功能,旧功能有没有保留?

C语言中 const 与 C++的 const 的区别?

C语言中的const有什么功能?

C++的 const 有什么新功能,旧功能有没有保留?

四、单例模式

什么是单例模式

只能创建出一个对象的结构、联合、类叫单例模式。

什么时候需要使用单例模式

1、Windows系统的任务管理器。

2、网络或服务器程序的访问计数器。

3、线程池、数据池,一个程序中只能创建一个线程池或数据池。

单例模式的实现原理

1、把构造函数、拷贝构造设置成私有的,不允许创建类对象。

2、在类中声明+定义一个静态的类对象(饿汉单例) 或类对象指针(懒汉单例)。

3、提供一个静态成员接口,用于获取静态的类对象。

饿汉单例模式

只要程序一运行,就把单例类的类对象创建出来,不管后续是否使用到。

缺点:如果后续程序使用不到单例对象,就造成了资源、时间上的浪费。

优点:线程安全,再多的线程也不可能创建出多个类对象。

懒汉单例

直到调用获取单例类对象的接口才会把对象创建出来。

缺点:如果在多线程情况下,多个线程同时调用对象的获取接口,可能会创建出多个类对象。

优点:如果用不到单例对象就不会真正创建,节约资源和时间。

单例模式的实现原理

1、把构造函数、拷贝构造设置成私有的,不允许创建类对象。

2、在类中声明+定义一个静态的类对象(饿汉单例) 或类对象指针(懒汉单例)。

3、提供一个静态成员接口,用于获取静态的类对象。

练习1:实现饿汉单例模式。

#include <iostream>
using namespace std;
​
class HungrySingle
{// 声明静态对象static HungrySingle obj;
​// 把构造和拷贝构造设置为private,可以防止创建类对象HungrySingle(void) {cout << "创建了类对象" << endl;}HungrySingle(const HungrySingle& that) {}
public:// 只能调用该接口获取静态类对象static HungrySingle& getSingleObject(void){return obj;}void show(void){cout << this << endl;}
};
​
// 定义静态对象
HungrySingle HungrySingle::obj;
​
int main(int argc,const char* argv[])
{cout << "main run ..." << endl;HungrySingle& h1 = HungrySingle::getSingleObject();HungrySingle& h2 = HungrySingle::getSingleObject();HungrySingle& h3 = HungrySingle::getSingleObject();h1.show();h2.show();h3.show();
​return 0;
}

练习2:实现懒汉单例模式。

#include <iostream>
using namespace std;
​
class LazySingle
{static LazySingle* obj;LazySingle(void){cout << "创建单例对象" << endl;}LazySingle(const LazySingle& that){}
public:static LazySingle* getSingleObject(void){if(NULL == obj){obj = new LazySingle;}return obj;}void show(void){cout << this << endl;}
};
LazySingle* LazySingle::obj;
​
int main(int argc,const char* argv[])
{cout << "main run ..." << endl;LazySingle* l1 = LazySingle::getSingleObject();LazySingle* l2 = LazySingle::getSingleObject();LazySingle* l3 = LazySingle::getSingleObject();l1->show();l2->show();l3->show();return 0;
}

任务1:线程安全的懒汉单例。

任务2:把PCM模型改为单例模式。

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

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

相关文章

Leetcode Hot 100刷题记录 -Day3(双指针)

移动零 问题描述&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0]示例 2:…

Qt杂项功能实现

本文介绍Qt杂项功能实现。 1.隐藏鼠标指针 1)整个应用程序都不显示鼠标指针 可在“QApplication a(argc, argv);”之后添加&#xff1a; QApplication::setOverrideCursor(Qt::BlankCursor); 这在带触摸屏的应用中非常有用。 2)某个窗口不显示鼠标指针 可在窗口的构造函…

JVM垃圾回收算法:标记-清除算法 、复制算法、 标记-整理算法、 分代收集算法

文章目录 引言I 标记回收算法(Mark-Sweep)算法不足II 复制算法(Copying)III 标记整理算法(Mark-Compact)IV 分代收集(以上三种算法的集合体)内存划分新生代算法:Minor GC老年代算法V 查看JVM堆分配引言 垃圾回收(Garbage Collection,GC) Java支持内存动态分配、…

Flask+LayUI开发手记(六):树型表格的增删改查

树型表格的增删改查功能与数据表格的是完全一致&#xff0c;就是调用layui-form表单组件实现数据输入再提交&#xff0c;比较大的区别是树型节点的编辑&#xff0c;都需要有上级节点的输入&#xff0c;而这个上级节点的展示&#xff0c;必须是以树型方式展示出来。当然&#xf…

【大数据】Kaggle:数据科学家的竞技场与学习平台

文章目录 一、引言二、Kaggle是什么&#xff1f;Kaggle的核心特点包括&#xff1a; 三、Kaggle的用途四、如何使用Kaggle1. 注册账号2. 探索竞赛3. 下载数据集4. 使用Kernels5. 参与论坛讨论 五、Kaggle的高级功能1. Kaggle Kernels2. 数据集3. 论坛 六、Kaggle在教育和企业中的…

语音控制开关的语音识别ic芯片方案

语音控制开关是一种基于语音识别技术的设备&#xff0c;它通过内置的语音识别芯片&#xff0c;将用户的语音指令转化为电信号&#xff0c;从而实现对设备的控制。例如在智能家居设备上的应用&#xff0c;通常需要连接到家庭的Wi-Fi网络上&#xff0c;以便与智能手机或智能音箱等…

python os获取当前git目录的git用户

要获取当前目录下的 Git 用户配置&#xff08;即用户名称和电子邮件&#xff09;&#xff0c;你可以结合操作系统命令和 git 命令来完成这一任务。以下是使用 Python 的 os 模块和 subprocess 模块来获取当前 Git 目录的用户配置的示例代码&#xff1a; 使用 Python 脚本&…

golang RSA 解密前端jsencrypt发送的数据时异常 crypto/rsa: decryption error 解决方法

golang中 RSA解密前端&#xff08;jsencrypt&#xff09;发来的密文后出现 "crypto/rsa: decryption error" &#xff0c; 这个问题首先需要确认你的私匙和公匙是否匹配&#xff0c; 如果匹配 那检查入参数据类型&#xff0c; 前端发送来的rsa加密后的数据一般都是…

bbr 随机 phase 的麻烦与 inflight 守恒算法的动机

bbr 有个要点&#xff0c;要把 probebw 的 phase 错开&#xff1a; static void bbr_reset_probe_bw_mode(struct sock *sk) {struct bbr *bbr inet_csk_ca(sk);bbr->mode BBR_PROBE_BW;bbr->cycle_idx CYCLE_LEN - 1 - prandom_u32_max(bbr_cycle_rand);bbr_advance…

【Java设计模式】集合管道模式:简化数据操作

文章目录 【Java设计模式】集合管道模式&#xff1a;简化数据操作一、概述二、集合管道设计模式的意图三、集合管道模式的详细解释及实际示例四、Java中集合管道模式的编程示例五、何时在Java中使用集合管道模式六、集合管道模式在Java中的实际应用七、集合管道模式的优点和权衡…

【封装自己的库】

封装自己的库 课前准备 工具 编辑器 VSCode浏览器 Chorme 前置知识 Js基本知识 课堂主题 一、定义函数返还JQ对象 二、ready方法和原生节点处理 三、选择器器封装 四、封装JQ的eq方法 五、封装JQ的click方法 六、jQ中的链式操作 七、封装JQ的css方法 八、cssHooks扩…

Java项目:基于SpringBoot+mysql在线拍卖系统(含源码+数据库+答辩PPT+毕业论文)

一、项目简介 本项目是一套基于SSM框架mysql在线拍卖系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能齐全、…

Java 面试题:HTTP版本演变--xunznux

文章目录 HTTP版本演变HTTP/0.9HTTP/1.0HTTP/1.1新引入&#xff1a;问题&#xff1a;长连接是什么&#xff1a;管道网络传输&#xff1a;队头阻塞是什么&#xff1f;解决http队头阻塞的方法&#xff1a;HTTP1.1常见性能问题为解决HTTP1.1性能问题而提出的常见优化手段 HTTP/21、…

【河北航空-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

RS485与USB3.1电路

USB2.0最高也就480M&#xff0c;而USB3.0轻松到达5Gbps&#xff1a; DTU远程控制&#xff1a; DTU&#xff08;数据传输单元&#xff09;通常用于将数据从现场设备传输到远程服务器&#xff0c;常用于物联网、工业控制、远程监控等场景。它可以通过GPRS/4G、Wi-Fi、以太网等方…

Python网络爬虫模拟登录与验证解析

内容导读 使用Selenium模拟登录 使用Cookies登录网站 模拟表单登录网站 爬虫识别简单的验证码 实例解析 一、使用Selenium模拟登录 1、为什么要模拟登录 在互联网上存在大量需要登录才能访问的网站&#xff0c;要爬取这些网站&#xff0c;就需要学习爬虫的模拟登录。对…

TC-RAG: Turing-Complete RAG--图灵完备的检索增强

摘要&#xff1a; 在提升领域特定的大语言模型&#xff08;LLMs&#xff09;的方法中&#xff0c;检索增强生成&#xff08;RAG&#xff09;技术作为一种有前景的解决方案&#xff0c;可以缓解诸如幻觉、知识过时以及在高度专业化查询中专业知识有限等问题。然而&#xff0c;现…

WPF- vs中的WPF应用项目模板 如何自己实现

读书笔记 1. 单个 c#文件的 空白window应用程序 (只展示了一个button按钮) 2.C#文件 和xml文件 的空白window程序 .xml文件作为程序的资源 (只一个button按钮) 3. xmal和c#共同编译 形如使用VS 创建WPF应用项目模板 1.新建一个wpf空白项目 ,添加一个主c#文件 和xaml文件(属…

【C++ Primer Plus习题】7.2

问题: 解答: #include <iostream> using namespace std;#define MAX 10int input(float* grade, int len) {int i 0;for (i 0; i < len; i){cout << "请输入第" << i 1 << "个高尔夫成绩(按0结束):";cin >> grade[i]…

【二叉树进阶】--- 前中后序遍历非递归

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey 本篇博客我们将来了解有关二叉树前中后序遍历的非递归版本。 &#x1f3e0; 前序遍历 要迭代非递归实现二叉树的前序遍历&#xff0c;首先还…