(十二)nodejs循序漸進-高性能游戲服務器框架pomelo之創建一個游戲聊天服務器

? ? ? ? 上個章節我們簡單介紹了下pomelo的安裝和目錄結構,有讀者可能覺得有點吃不消,為什么不再深入講一講目錄結構和里邊的庫,這里我就不費口舌了,大家可以去官網參考文檔說明,本文只告訴大家如何利用這個框架來開發自己的東西。

隨著文章的后續不斷推進,我相信大家會越來越熟悉pomelo,對豬場框架的使用也會越來越得心用手。

為什么是聊天服務器?

我們目標是搭建游戲服務器,為什么從聊天開始呢?

聊天可認為是簡化的實時游戲,它與游戲服務器有著很多共通之處,如實時性、頻道、廣播等。由于游戲在場景管理、客戶端動畫等方面有一定的復雜性,并不適合作為 pomelo 的入門應用。聊天應用通常是 Node.js 入門接觸的第一個應用,因此更適合做入門教程。

一個聊天系統我們設計思路是客戶端連接gate網關服務器,由gate網關服務器根據玩家的uid的crc32的校驗碼與connector服務器的個數取余,從而得到一個connector服務器,把這個connector服務器分配給請求用戶,那么客戶端就可以通過此connector服務器建立連接,而和connector服務器保持連接的是chat邏輯服務器,所有的邏輯處理交給connector發起remote的RPC調用。

? ?

新建gate和chat服務器

在app/servers目錄下新建gate和chat服務器。

gate服務器:

?在一般情況下用戶量一臺機器就可以支撐,但用戶量多了就得橫向擴充服務器(在gate服務器之前通過nginx反向代理做端口轉發,相關文章可以參考我之前的 nginx+apache專欄),gate服務器的作用就相當于前端負載均衡服務器;

?客戶端向gate服務器發出請求,gate服務器會給客戶端分配一個connector服務器;

?分配策略是根據客戶端的某一個key做hash得到connector的id,這樣就可以實現各個connector服務器的負載均衡。?

?connector服務器:?

?接受客戶端請求,并將其路由到chat服務器,以及維護客戶端的鏈接;

?同時,接收客戶端對后端服務器的請求,按照用戶配置的路由策略,將請求路由給具體的后端服務器。當后端服務器處理完請求或者需要給客戶端推送消息的時候,connector服務器同樣會扮演一個中間角色,完成對客戶端的消息發送;

?connector服務器會同時擁有clientPort和port,其中clientPort用來監聽客戶端的連接,port端口用來給后端提供服務;

?chat服務器:

?handler和remote決定了服務器的行為;

?handler接收用戶發送過來的send請求,remote由connector RPC發起遠程調用時調用;

?在remote里由于涉及到用戶的加入和退出,所以會有對channel的操作。

配置master.json

在config下打開master.json

{"development": {"id": "master-server-1", "host": "127.0.0.1", "port": 3005},"production": {"id": "master-server-1", "host": "127.0.0.1", "port": 3005}
}

?

?配置servers.json

? 打開config目錄下servers.json文件,配置好各種 type?的服務器,配置如下

{"development":{"connector":[{"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true},{"id":"connector-server-2", "host":"127.0.0.1", "port":4051, "clientPort": 3051, "frontend": true},{"id":"connector-server-3", "host":"127.0.0.1", "port":4052, "clientPort": 3052, "frontend": true}],"chat":[{"id":"chat-server-1", "host":"127.0.0.1", "port":6050},{"id":"chat-server-2", "host":"127.0.0.1", "port":6051},{"id":"chat-server-3", "host":"127.0.0.1", "port":6052}],"gate":[{"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true}]},"production":{"connector":[{"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true},{"id":"connector-server-2", "host":"127.0.0.1", "port":4051, "clientPort": 3051, "frontend": true},{"id":"connector-server-3", "host":"127.0.0.1", "port":4052, "clientPort": 3052, "frontend": true}],"chat":[{"id":"chat-server-1", "host":"127.0.0.1", "port":6050},{"id":"chat-server-2", "host":"127.0.0.1", "port":6051},{"id":"chat-server-3", "host":"127.0.0.1", "port":6052}],"gate":[{"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true}]}
}

?

? 解釋一下配置中的各字段:

??id:???字符串類型的應用服務器ID

??host:應用服務器的IP或者域名

??port:RPC請求監聽的端口

??clientPort:?前端服務器的客戶端請求的監聽端口

??frontend:bool類型,是否是前端服務器,默認:?false

? 可選參數:

??max-connections:前端服務器最大客戶連接數

??args:?node/v8配置,如配置為"args": "--debug=5858 "這樣就可以啟用項目調試(沒用過,臨時問了一下谷歌,看別人是這么解釋的^_^!)

?配置adminServer.json

打開config目錄下adminServer.json文件,配置好各種 type?的服務器,adminServer.json的使用是讓指定type的服務器通過token去向master注冊。
master是框架組件,在poemlo.start()時首先被啟動,然后由它負責啟動其他的組件,包括系統組件和servers.json 中的用戶配置組件。
servers.json 中的組件被啟動,要向master注冊報告自己已經啟動了。
在報告的時候需要通過consoleService的authserver的token進行驗證。
這個authserver的token就在adminServer.json中
若是沒有對對應的type配置對應的token,那么這個服務器就無法注冊到master。
這個功能在 node_modules/pomelo/node_modules/pomelo-admin中實現。
因此,你server.json中有幾種服務器,那么就得在adminserver.json中配置對應的type與token.

進入config/adminServer.json

[{"type": "connector","token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
}, {"type": "chat","token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
},{"type": "gate","token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
}]

解決服務器分配問題

?從上面的servers.json配置的修改可以看出與最開始創建出來的項目一個服務器相比,connector和chat我都配置了三個服務器

?這就要解決客戶端請求服務器分配問題

?解決思路:用戶訪問gate服務器,使用用戶的uid的crc32的校驗碼與connector服務器的個數取余,從而得到一個connector服務器,把這個connector服務器分配給請求用戶

?在app目錄下新建util目錄,目錄下新建“dispatcher.js”和 “routeUtil.js”文件,處理此服務器分配邏輯

dispatcher.js

var crc = require('crc');module.exports.dispatch = function(uid, connectors) {var index = Math.abs(crc.crc32(uid)) % connectors.length;return connectors[index];
};

routeUtil.js

var dispatcher = require('./dispatcher');module.exports.chat = function(session, msg, app, cb) {var chatServers = app.getServersByType('chat');if(!chatServers || chatServers.length === 0) {cb(new Error('can not find chat servers.'));return;}var res = dispatcher.dispatch(session.get('rid'), chatServers);cb(null, res.id);
};

? 準備好這些文件后,在game-server服務器入口文件app.js中添加配配置:

?注意:

? ?app.configure('production|development', 'connector', function(){? ?

? ?修改為

? ?app.configure('production|development',? function(){

? ?這個如果不修改,在啟動調用時會遇到?engine.io?中報錯??TypeError: Cannot read property??'indexOf' of undefined? at Server.verify .

?這里我們使用的connector是sioconnector(支持socket.io)?

這里我有必要說明下route的API:

API說明
route(serverType, routeFunc)

?Application.route(); serverType:服務類型;routeFunc:路由功能函數,如:routeFunc(session, msg, app, cb)

未指定的服務類型設置路由功能。如:

app.route('area', routeFunc);

var routeFunc = function(session, msg, app, cb) {

  // all request to area would be route to the first area server

  var areas = app.getServersByType('area');

  cb(null, areas[0].id);

}

另外注意的是transports這個參數:這個配置選項是用于sioconnector的,因為socket.io的通信方式可能會有多種,如websocket,xhr-polling等等。通過這個配置選項可以選擇需要的方式。?

配置connector組件,通過調用如下方式進行:

app.set('connectorConfig', opts);

知道了以上基礎知識,那么我們直接上代碼“:?

var pomelo = require('pomelo');
var routeUtil = require('./app/util/routeUtil');
/*** Init app for client.*/
var app = pomelo.createApp();
app.set('name', 'demoserver');// app configure
app.configure('production|development', function() {// route configuresapp.route('chat', routeUtil.chat);app.set('connectorConfig', {connector: pomelo.connectors.sioconnector,// 'websocket', 'polling-xhr', 'polling-jsonp', 'polling'transports: ['websocket', 'polling'],heartbeats: true,closeTimeout: 60 * 1000,heartbeatTimeout: 60 * 1000,heartbeatInterval: 25 * 1000});// filter configuresapp.filter(pomelo.timeout());
});// start app
app.start();process.on('uncaughtException', function(err) {console.error(' Caught exception: ' + err.stack);
});

實現?gate.gateHandler

? 作用:用戶連接gate服務器,返回分配的connector

? 在gate目錄下handler下新建gateHandler.js,代碼如下

var dispatcher = require('../../../util/dispatcher');module.exports = function(app) {return new Handler(app);
};var Handler = function(app) {this.app = app;
};var handler = Handler.prototype;/*** Gate handler that dispatch user to connectors.** @param {Object} msg message from client* @param {Object} session* @param {Function} next next stemp callback**/
handler.queryEntry = function(msg, session, next) {var uid = msg.uid;if(!uid) {next(null, {code: 500});return;}// get all connectorsvar connectors = this.app.getServersByType('connector');if(!connectors || connectors.length === 0) {next(null, {code: 500});return;}// select connectorvar res = dispatcher.dispatch(uid, connectors);next(null, {code: 200,host: res.host,port: res.clientPort});
};

其中handler.queryEntry是到時候客戶端請求訪問的路由接口,如果讀者對此處有疑惑的可以閱讀我上一篇文章里的架構部分,有專門對route路由規則介紹的。

?

實現connector中entryHandler.js

? 主要完成接受客戶端的請求,維護與客戶端的連接,路由客戶端的請求到chat服務器;

在原有的entryHandler.js基礎上做修改,加入enter進入聊天室的接口.

這里我要說明下:

在chat服務器里創建了channel, 這個channel只有在chat服務器里能用.
別的服務器里, 獲取不到chat服務器的channel, 不過你可以通過RPC方式調用chat服務器的方式來獲取其屬性。

module.exports = function(app) {return new Handler(app);
};var Handler = function(app) {this.app = app;
}; 
/*** New client entry chat server.** @param  {Object}   msg     request message* @param  {Object}   session current session object* @param  {Function} next    next stemp callback* @return {Void}*/
Handler.prototype.enter = function(msg, session, next) {var self = this;var rid = msg.rid;var uid = msg.username + '*' + ridvar sessionService = self.app.get('sessionService');//duplicate log inif( !! sessionService.getByUid(uid)) {next(null, {code: 500,error: true});return;}session.bind(uid);session.set('rid', rid);session.push('rid', function(err) {if(err) {console.error('set rid for session service failed! error is : %j', err.stack);}});session.on('closed', onUserLeave.bind(null, self.app));//put user into channelself.app.rpc.chat.chatRemote.add(session, uid, self.app.get('serverId'), rid, true, function(users){next(null, {users:users});});
};/*** User log out handler** @param {Object} app current application* @param {Object} session current session object**/
var onUserLeave = function(app, session) {if(!session || !session.uid) {return;}app.rpc.chat.chatRemote.kick(session, session.uid, app.get('serverId'), session.get('rid'), null);
};
/*** New client entry.** @param  {Object}   msg     request message* @param  {Object}   session current session object* @param  {Function} next    next step callback* @return {Void}*/
Handler.prototype.entry = function(msg, session, next) {next(null, {code: 200, msg: 'game server is ok.'});
};/*** Publish route for mqtt connector.** @param  {Object}   msg     request message* @param  {Object}   session current session object* @param  {Function} next    next step callback* @return {Void}*/
Handler.prototype.publish = function(msg, session, next) {var result = {topic: 'publish',payload: JSON.stringify({code: 200, msg: 'publish message is ok.'})};next(null, result);
};/*** Subscribe route for mqtt connector.** @param  {Object}   msg     request message* @param  {Object}   session current session object* @param  {Function} next    next step callback* @return {Void}*/
Handler.prototype.subscribe = function(msg, session, next) {var result = {topic: 'subscribe',payload: JSON.stringify({code: 200, msg: 'subscribe message is ok.'})};next(null, result);
};

? 這里完成的主要就是RPC遠程調用chat服務器chatRemote中的實現(通過app.rpc.chat.chatRemote來拉起RPC調用,我們將在下面實現chat服務器的remote接口)。

這里我有必要說下為什么要用rpc方式,而不是用socket.io 方式或者websocket方式:

?比如我們游戲的角色信息是作為一個redis緩存對方存放的,有一個不好的地方就是,如果別的地方調用該玩家的信息并進行修改,就可能會出現兩處數據修改,結果卻只有一處能夠修改成功。

例如:A接口,B接口。都會獲取charInfo并對charInfo 進行修改。
先調用A接口, 在A接口處理邏輯的過程中,調用了B接口。
這時候A,B獲取到的charInfo是一樣的,但是,修改的屬性值可能不一樣,在redis設置緩存的時候,只能是哪個最后設置的,charInfo 修改的屬性值才會生效。比如A修改exp屬性,B修改gold屬性。
A接口先設置緩存,B后設置。這時候,只有gold屬性值才會被真正的修改。因為在B獲取到的charInfo里沒有修改charInfo.exp. 說白了,就是不是修改的同一個對象。

所以這次我們將角色信息專門定義了一個char對象,提供一個get和一個set接口。這樣,不論是那個地方對charInfo 的修改,都是針對同一個對象的修改。

char對象定義放在data服務器,其他服務器例如chat服務器,要獲取charInfo ,就需要rpc調用data服務器的get方法獲取charInfo. 如果在chat服務器里有對charInfo進行修改,則一定要rpc調用data服務器的set方法,重新設置charInfo. 如果data是單個的服務器就沒有必要。不過一般至少有三個data服務器。

玩家在登陸的時候,分配一個data服務器給當前玩家。該玩家的信息就保存在這個data服務器的session里。其他data服務器不會有。這也是為什么其他非data服務器對charInfo 修改一定要遠程rpc set一下. 因為在其他非data服務器下對charInfo 的修改,都修改的不是一個不同的對象了。一個服務器一個session,一個charInfo保存在一個服務器的session, 這樣就好理解點了。

?

?實現chat服務器chatRemote.js?

?chat服務器會接受connector的遠程調用,完成channel維護中的用戶的加入以及離開。

這里大家需要再次了解這兩個模塊的API ChannelServiceChannel

ChannelService?

    創建和維護本地服務的信道。

API說明
createChannel(name)ChannelService.prototype.createChannel() 根據信道名稱創建信道,如果該信道已存在則返回已存在的信道
getChannel(name,create)ChannelService.prototype.getChannel() name:信道名稱,create:如果為true,并且信道不存在時,則創建新的信道。根據信道名稱獲取信道
destroyChannel(name)ChannelService.prototype.destroyChannel() 根據信道名稱,刪除信道
pushMessageByUids(route, msg, uids, cb)ChannelService.prototype.pushMessageByUids() route:消息路由;msg:發送到客戶端的消息;uids:接收消息的客戶端列表,格式?[{uid: userId, sid: frontendServerId}];cb:回調函數 cb(err)。根據uids將消息推送給客戶端,如果uids中的sid未指定,則忽略相應的客戶端
broadcast(stype,route, msg, opts, cb)ChannelService.prototype.broadcast()?stype:前端服務的類型;route:路由;msg:消息;opts:廣播參數;cb:回調函數。廣播消息到所有連接的客戶端。

Channel

API說明
add(uid,sid)Channel.prototype.add() uid:用戶編號;sid:用戶連接到的前端服務id。添加指定用戶到信道。
leave(uid,sid)Channel.prototype.leave()?uid:用戶編號;sid:用戶連接到的前端服務id。從信道中移除用戶。
getMembers()Channel.prototype.getMembers() 獲得信道中的成員
getMember(uid)Channel.prototype.getMember() 根據uid獲取成員信息
destroy()Channel.prototype.destroy() 銷毀信道
pushMessage(route,msg,cb)Channel.prototype.pushMessage() ?route:消息路由,msg:要推送的消息,cb:回調函數。將消息推送給信道的所有成員。
module.exports = function(app) {return new ChatRemote(app);
};var ChatRemote = function(app) {this.app = app;this.channelService = app.get('channelService');
};/*** Add user into chat channel.** @param {String} uid unique id for user* @param {String} sid server id* @param {String} name channel name* @param {boolean} flag channel parameter**/
ChatRemote.prototype.add = function(uid, sid, name, flag, cb) {var channel = this.channelService.getChannel(name, flag);var username = uid.split('*')[0];var param = {route: 'onAdd',user: username};channel.pushMessage(param);if( !! channel) {channel.add(uid, sid);}cb(this.get(name, flag));
};/*** Get user from chat channel.** @param {Object} opts parameters for request* @param {String} name channel name* @param {boolean} flag channel parameter* @return {Array} users uids in channel**/
ChatRemote.prototype.get = function(name, flag) {var users = [];var channel = this.channelService.getChannel(name, flag);if( !! channel) {users = channel.getMembers();}for(var i = 0; i < users.length; i++) {users[i] = users[i].split('*')[0];}return users;
};/*** Kick user out chat channel.** @param {String} uid unique id for user* @param {String} sid server id* @param {String} name channel name**/
ChatRemote.prototype.kick = function(uid, sid, name, cb) {var channel = this.channelService.getChannel(name, false);// leave channelif( !! channel) {channel.leave(uid, sid);}var username = uid.split('*')[0];var param = {route: 'onLeave',user: username};channel.pushMessage(param);cb();
};

?可以看到上面代碼中的add和kick分別對應著加入和離開channel

?實現chat服務器chatHandler.js

?chat服務器執行聊天邏輯,維護channel信息,一個房間就是一個channel,一個channel里有多個用戶,當有用戶發起聊天的時候,就會將其內容廣播到整個channel。

var chatRemote = require('../remote/chatRemote');module.exports = function(app) {return new Handler(app);
};var Handler = function(app) {this.app = app;
};var handler = Handler.prototype;/*** Send messages to users** @param {Object} msg message from client* @param {Object} session* @param  {Function} next next stemp callback**/
handler.send = function(msg, session, next) {var rid = session.get('rid');var username = session.uid.split('*')[0];var channelService = this.app.get('channelService');var param = {route: 'onChat',msg: msg.content,from: username,target: msg.target};channel = channelService.getChannel(rid, false);//the target is all usersif(msg.target == '*') {channel.pushMessage(param);}//the target is specific userelse {var tuid = msg.target + '*' + rid;var tsid = channel.getMember(tuid)['sid'];channelService.pushMessageByUids(param, [{uid: tuid,sid: tsid}]);}next(null, {route: msg.route});
};

?這里面是發送消息(給房間內所有人和指定用戶)

?

10.運行

? 到此這個聊天服務器實現就完成,?打開命令行工具,執行沒有錯誤信息,基本就成功了!

cd game-server目錄
pomelo start

進入webserver 執行node app

瀏覽器輸入http://localhost:3001/

登錄用戶信息:

登錄其他用戶,互發消息:

源碼資源可通過 此鏈接 下載(目前僅限關注的粉絲下載)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/444536.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/444536.shtml
英文地址,請注明出處:http://en.pswp.cn/news/444536.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

看這玩意復習你還會掛科?《軟件工程篇》

軟件工程&#xff1a;是指導軟件開發和維護的一門工程學科 三要素方法/工具/開發過程 價值&#xff1a;促進項目成功 現代產品開發三原則&#xff1a;功用性、可行性、稱許性 軟件過程是軟件工程的核心組成部分。 迭代 &#xff1a;反復求精 增量&#xff1a;逐塊建造 需…

C++:02---命名空間

一、概念: ①類似于倉庫,空間內存儲代碼,需要用到時調用②也為防止名字沖突提供了更加可控的機制二、命名空間的定義 定義的基本格式如下:namespace 命名空間名 { //一系列聲明與定義 };三、命名空間的注意事項 命名空間定義時最后的分號可有可無只要出現在全局作用域中的…

看這玩意復習你還會掛科?《軟件工程2篇》

第一章&#xff1a; 軟件工程定義&#xff1a; 1968年10月&#xff0c;Fritz Bauer 首次提出了“軟件工程”的概念&#xff0c;并將“軟件工程”定義為&#xff1a;為了經濟地獲得能夠在實際機器上有效運行的可靠軟件&#xff0c;而建立并使用的一系列工程化原則。 1993年IE…

C++:05---命名空間

一、概念: ①類似于倉庫,空間內存儲代碼,需要用到時調用②也為防止名字沖突提供了更加可控的機制二、命名空間的定義 定義的基本格式如下:namespace 命名空間名 { //一系列聲明與定義 };三、命名空間的注意事項 命名空間定義時最后的分號可有可無只要出現在全局作用域中的…

C++:04---內聯函數

1.概念: 內聯類似于宏定義,當程序執行到內聯函數時,相當于復制了一份函數代碼。犧牲代碼空間,贏得了時間 內聯說明只是向編譯器發出一個請求,編譯器可以選擇忽略這個請求 2.關鍵字:inline 聲明時寫了inline,定義時可省略。建議聲明和定義都加上inlineinline int add(int…

leetcode86. 分隔鏈表

給定一個鏈表和一個特定值 x&#xff0c;對鏈表進行分隔&#xff0c;使得所有小于 x 的節點都在大于或等于 x 的節點之前。 你應當保留兩個分區中每個節點的初始相對位置。 示例: 輸入: head 1->4->3->2->5->2, x 3 輸出: 1->2->2->4->3->5…

(十三)nodejs循序漸進-高性能游戲服務器框架pomelo之擴展聊天服務器為機器人自動聊天

聊天服務器擴展 大家在上一篇文章里相信已經學會了pomelo框架的基本用法了&#xff0c;那么我們在上一篇文章的代碼基礎上繼續擴展&#xff0c;豐富系統&#xff0c;另外也熟悉下他的更多的用法&#xff0c;這一節我將擴展它&#xff1a;增加一個機器人自動聊天的功能。 目的…

C++:09---類靜態成員、類常量成員

一、類靜態成員(static) 先介紹一下什么是靜態變量、靜態函數 靜態局部變量:存在域(全局數據區),作用域(塊作用域)靜態全局變量:存在域(全局數據區),作用域(整個文件)靜態函數:存在域(全局數據區),作用域(整個文件)static int a=10;//全局靜態變量 static vo…

C++:08---成員變量初始化方式

成員變量初始化有三種方式: 在構造函數體內賦值初始化在自定義的公有函數體中賦值初始化(一般用于成員變量的初始化)在構造函數的成員初始化列表初始化一、構造函數體內初始化 說明:在構造函數體內的初始化方式,本質是是為成員變量賦值,而不是真正意義上的初始化,這點要…

leetcode1290. 二進制鏈表轉整數 刷新認知,最簡單算法題

給你一個單鏈表的引用結點 head。鏈表中每個結點的值不是 0 就是 1。已知此鏈表是一個整數數字的二進制表示形式。 請你返回該鏈表所表示數字的 十進制值 。 示例 1&#xff1a; 輸入&#xff1a;head [1,0,1] 輸出&#xff1a;5 解釋&#xff1a;二進制數 (101) 轉化為十進…

Redis:02---安裝Redis(Linux+Windows+Docker)

Linux安裝&#xff1a;一、安裝方式1&#xff08;下載源碼編譯安裝&#xff09;第一步&#xff1a;從下面的網址中下載Redis最新穩定版本的源代碼sudo wget http://download.redis.io/redis-stable.tar.gz第二步&#xff1a;下載完之后解壓&#xff0c;建立一個軟鏈接指向于red…

C++:10---再議拷貝構造函數

一、概念 使用一個已經存在的對象,去構造(初始化)另一個對象二、格式 參數加上const&,因為拷貝構造函數在幾種情況下都會被隱式地使用,因此拷貝構造函數不應該是explict的const:防止函數內部修改值&:防止無限循環拷貝類名(類名 const& 參數名) { 函數體 }三、…

人的思維謬誤與心理學效應

啟發法 用一個容易的問題代替難以回答的真正問題。這個容易的問題的答案就是對真正問題的啟發&#xff0c;但啟發經常和真正的答案差得很遠&#xff0c;而人卻往往把啟發當成了真正問題的答案。 接下來介紹和啟發法相關的心理效應和謬誤。每一個謬誤都會注明真正的問題是什么…

C++:07---this指針

一、this指針介紹 概念:this指針是成員函數的一個隱式參數,在類中本質上就是對象的指針(常量指針)特點:在成員函數中可通過this指針區別成員變量與形參變量this可以顯式調用示例代碼:class Cperson { private: int age; float height; public: void InitPerson(int age,flo…

Redis :01---Redis簡介和安裝

一、Redis簡介 Redis官網&#xff1a;https://redis.io/ Redis是一種基于鍵值對&#xff08;key-value&#xff09;的NoSQL數據庫 與很多鍵值對數據庫不同的是&#xff0c;Redis中的值可以是由string&#xff08;字符串&#xff09;、hash&#xff08;哈希&#xff09;、 list&…

215. 數組中的第K個最大元素 BFPRT最牛解法

在未排序的數組中找到第 k 個最大的元素。請注意&#xff0c;你需要找的是數組排序后的第 k 個最大的元素&#xff0c;而不是第 k 個不同的元素。 示例 1: 輸入: [3,2,1,5,6,4] 和 k 2 輸出: 5 示例 2: 輸入: [3,2,3,1,2,4,5,5,6] 和 k 4 輸出: 4 說明: 你可以假設 k 總是…

C++: 06---構造函數析構函數

拷貝構造函數: 用一個已經存在的對象來生成一個相同類型的新對象。(淺拷貝)默認的拷貝構造函數: 如果自定義了拷貝構造函數,編譯器就不在生成默認的拷貝構造函數。 如果沒有自定義拷貝構造函數,但在代碼中用到了拷貝構造函數,編譯器會生成默認…

leetcode371. 兩整數之和 不用+號做加法

不使用運算符 和 - &#xff0c;計算兩整數 ???????a 、b ???????之和。 示例 1: 輸入: a 1, b 2 輸出: 3 示例 2: 輸入: a -2, b 3 輸出: 1 思路&#xff1a;模擬加法器 二進制不考慮進位&#xff1a;000&#xff0c;010&#xff0c;110&#xff0c;是…

C++:05---class和struct

C++被稱為“C with class”,可見在C++中class是多么重要,與class類似的一個結構就是struct了,struct最早是在C語言中出現的,在C++中對struct的功能也進行了擴展。 class : public(公有):在類內外、派生類中都可被訪問protected(保護):希望與派生類共享但是不想被公共…

leetcode34. 在排序數組中查找元素的第一個和最后一個位置

給定一個按照升序排列的整數數組 nums&#xff0c;和一個目標值 target。找出給定目標值在數組中的開始位置和結束位置。 你的算法時間復雜度必須是 O(log n) 級別。 如果數組中不存在目標值&#xff0c;返回 [-1, -1]。 示例 1: 輸入: nums [5,7,7,8,8,10], target 8 輸…