Linux并發服務器編程之多線程并發服務器

轉載:http://blog.csdn.net/qq_29227939/article/details/53782198

上一篇文章使用fork函數實現了多進程并發服務器,但是也提到了一些問題:

  1. fork是昂貴的。fork時需要復制父進程的所有資源,包括內存映象、描述字等;
  2. 目前的實現使用了一種寫時拷貝(copy-on-write)技術,可有效避免昂貴的復制問題,但fork仍然是昂貴的;
  3. fork子進程后,父子進程間、兄弟進程間的通信需要進程間通信IPC機制,給通信帶來了困難;
  4. 多進程在一定程度上仍然不能有效地利用系統資源;
  5. 系統中進程個數也有限制。

??下面就介紹實現并發服務器的另外一種方式,使用多線程實現。多線程有助于解決以上問題。

線程基礎

??關于線程的概念就不介紹了,先了解一下linux下線程的一些基本操作。

線程基礎函數

  • pthread_create 創建線程

??pthread_create 函數用于創建新線程。當一個程序開始運行時,系統產生一個稱為初始線 程或主線程的單個線程。額外的線程需要由 pthread_create 函數創建。 pthread_create 函數原型如下:

#include <pthread.h> 
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg); 
  • 1
  • 2

??如果新線程創建成功,參數 tid 返回新生成的線程 ID。一個進程中的每個線程都由一個 線程 ID 標識,其類型為 pthread_t。attr 指向線程屬性的指針。每個線程有很多屬性包括:優 先級、起始棧大小、是否是守護線程等等。通常將 attr 參數的值設為 NULL,這時使用系統 默認的屬性。?
??但創建完一個新的線程后,需要說明它將執行的函數。函數的地址由參數 func 指定。該函數必須是一個靜態函數,它只有一個通用指針作為參數,并返回一個通用指針。該執行函 數的調用參數是由 arg 指定,arg 是一個通用指針,用于往 func 函數中傳遞參數。如果需要傳遞多個參數時,必須將它們打包成一個結構,然后讓 arg 指向該結構。線程以調用該執行 函數開始。?
??如果函數調用成功返回 0,出錯則返回非 0。

??常見的返回錯誤值:

EAGAIN:超過了系統線程數目的限制。
ENOMEN:沒有足夠的內存產生新的線程。
EINVAL:無效的屬性attr值。
  • 1
  • 2
  • 3

??示例代碼:

#include <pthread.h>
#include <stdio.h>
pthread_t  tid;
void *ex()
{printf("this is a thread");
}
void main()
{pthread_create(&tid,NULL,ex,NULL);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

??給線程傳遞參數:

void *function(void *arg); 
struct ARG { int connfd; int other;   //other data }; void main()  { struct ARG arg; int connfd,sockfd; pthread_t tid; //...While(1) {     if((connfd = accept(sockfd,NULL,NULL))== -1) { //handle exception                    } arg.connfd = connfd; if(pthread_create(&tid, NULL, funtion, (void *)&arg)) { // handle exception } } } void *funtion(void *arg) { struct  ARG info; info.connfd = ((struct ARG *)arg) -> connfd; info.other = ((struct ARG *)arg) -> other; //… close(info.connfd); pthread_exit(NULL); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • pthread_join?
    ??看這個函數首先提出一個概念,線程的類型。線程分為兩類:可聯合的和分離的。

    1. 默認情況下線程都是可聯合的。可聯合的線程終止 時,其線程 ID 和終止狀態將保留,直到線程調用 pthread_join 函數。
    2. 而分離的線程退出后, 系統將釋放其所有資源,其他線程不能等待其終止。如果一個線程需要知道另一個線程什么 時候終止,最好保留第二個線程的可聯合性。

??pthread_join 函數與進程的 waitpid 函數功能類似,等待一個線程終止。?
pthread_join 函數原型如下:

#inlcude <pthread.h>
int pthread_join(pthread_t tid, void **status); 
  • 1
  • 2

??參數 tid 指定所等待的線程 ID。該函數必須指定要等待的線程,不能等待任一個線程結束。要求等待的線程必須是當前進程的成員,并且不是分離的線程或守護線程。?
??幾個線程不 能同時等待一個線程完成,如果其中一個成功調用 pthread_join 函數,則其他線程將返回 ESRCH 錯誤。?
??如果等待的線程已經終止,則該函數立即返回。如果參數 status 指針非空,則 指向終止線程的退出狀態值。?
??該函數如果調用成功則返回 0,出錯時返回正的錯誤碼。

  • pthread_detach?
    ??pthread_detach 函數將指定的線程變成分離的。 pthread_detach 函數原型如下:
#inlcude <pthread.h> 
int pthread_detach(pthread_t tid) ;
  • 1
  • 2

??參數 tid 指定要設置為分離的線程 ID。

  • pthread_self?
    ??每一個線程都有一個 ID,pthread_self 函數返回自己的線程 ID。 pthread_self 函數原型如下:
#inlcude <pthread.h> 
pthread_t pthread_self(void); 
  • 1
  • 2

??參數 tid 指定要設置為分離的線程 ID。 函數返回調用函數的線程 ID。?
??例如,線程可以通過如下語句,將自己設為可分離的:

pthread_detach(pthread_self()); 
  • 1
  • pthread_exit?
    ??函數 pthread_exit 用于終止當前線程,并返回狀態值,如果當前線程是可聯合的,則其 退出狀態將保留。 pthread_exit函數原型如下:
#include <pthread.h> 
void pthread_exit(void *status);
  • 1
  • 2

??參數 status 指向函數的退出狀態。這里的 status 不能指向一個局部變量,因為當前線程 終止后,其所有局部變量將被撤銷。?
??該函數沒有返回值。?
??還有兩種方法可以使線程終止:

  1. 啟動線程的函數 pthread_create 的第三個參數返回。該返回值就是線程的終止狀態。
  2. 如果進程的 main 函數返回或者任何線程調用了 exit 函數,進程將終止,線程將隨之 終止。

??下面可以看一下多線程并發服務器的實例了,需要注意的是,線程建立后,父、子線程不需要關閉任何的描述符,因為線程中使用的描述符是共享進程中的數據。

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <pthread.h>  #define PORT 1234  
#define BACKLOG 5  
#define MAXDATASIZE 1000  void process_cli(int connfd, struct sockaddr_in client);  
void *function(void* arg);  
struct ARG {  int connfd;  struct sockaddr_in client;  
};  void main()  
{  int listenfd,connfd;  pthread_t  tid;  struct ARG *arg;  struct sockaddr_in server;  struct sockaddr_in client;  socklen_t  len;  if ((listenfd =socket(AF_INET, SOCK_STREAM, 0)) == -1) {  perror("Creatingsocket failed.");  exit(1);  }  int opt =SO_REUSEADDR;  setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  bzero(&server,sizeof(server));  server.sin_family=AF_INET;  server.sin_port=htons(PORT);  server.sin_addr.s_addr= htonl (INADDR_ANY);  if (bind(listenfd,(struct sockaddr *)&server, sizeof(server)) == -1) {  perror("Bind()error.");  exit(1);  }  if(listen(listenfd,BACKLOG)== -1){  perror("listen()error\n");  exit(1);  }  len=sizeof(client);  while(1)  {  if ((connfd =accept(listenfd,(struct sockaddr *)&client,&len))==-1) {  perror("accept() error\n");  exit(1);  }  arg = (struct ARG *)malloc(sizeof(struct ARG));  arg->connfd =connfd;  memcpy((void*)&arg->client, &client, sizeof(client));  if(pthread_create(&tid, NULL, function, (void*)arg)) {  perror("Pthread_create() error");  exit(1);  }  }  close(listenfd);  
}  void process_cli(int connfd, struct sockaddr_in client)  
{  int num;  char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];  printf("Yougot a connection from %s. \n ",inet_ntoa(client.sin_addr) );  num = recv(connfd,cli_name, MAXDATASIZE,0);  if (num == 0) {  close(connfd);  printf("Clientdisconnected.\n");  return;  }  cli_name[num - 1] ='\0';  printf("Client'sname is %s.\n",cli_name);  while (num =recv(connfd, recvbuf, MAXDATASIZE,0)) {  recvbuf[num] ='\0';  printf("Receivedclient( %s ) message: %s",cli_name, recvbuf);  int i;  for (i = 0; i <num - 1; i++) {  if((recvbuf[i]>='a'&&recvbuf[i]<='z')||(recvbuf[i]>='A'&&recvbuf[i]<='Z'))  {  recvbuf[i]=recvbuf[i]+ 3;  if((recvbuf[i]>'Z'&&recvbuf[i]<='Z'+3)||(recvbuf[i]>'z'))  recvbuf[i]=recvbuf[i]- 26;  }  sendbuf[i] =recvbuf[i];  }  sendbuf[num -1] = '\0';  send(connfd,sendbuf,strlen(sendbuf),0);  }  close(connfd);  
}  void *function(void* arg)  
{  struct ARG *info;  info = (struct ARG*)arg;  process_cli(info->connfd,info->client);  free (arg);  pthread_exit(NULL);  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113

線程安全性

??上面的示例代碼服務器端的業務邏輯都比較簡單,沒有涉及到共享數據產生的同步問題。在某些情況下,我們需要多個線程共享全局數據,在訪問這些數據時就需要用到同步鎖機制。而在共享線程內的全局數據時,可以使用Linux提供的線程特定數據TSD解決。

同步機制

??在linux系統中,提供一種基本的進程同步機制—互斥鎖,可以用來保護線程代碼中共享數據的完整性。?
??操作系統將保證同時只有一個線程能成功完成對一個互斥鎖的加鎖操作。?
??如果一個線程已經對某一互斥鎖進行了加鎖,其他線程只有等待該線程完成對這一互斥鎖解鎖后,才能完成加鎖操作。

互斥鎖函數

pthread_mutex_lock(pthread_mutex_t  *mptr)
  • 1

參數說明:

mptr:指向互斥鎖的指針。
該函數接受一個指向互斥鎖的指針作為參數并將其鎖定。如果互斥鎖已經被鎖定,調用者將進入睡眠狀態。函數返回時,將喚醒調用者。
如果互斥鎖是靜態分配的,就將mptr初始化為常值PTHREAD_MUTEX_INITIALIZER。 
  • 1
  • 2
  • 3

??鎖定成功返回0,否則返回錯誤碼。

pthread_mutex_unlock(pthread_mutex_t  *mptr);
  • 1

??用于互斥鎖解鎖操作。成功返回0,否則返回錯誤碼。

示例代碼:

#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int myglobal;
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
void *thread_function(void *arg) {int i, j;for (i = 0; i < 5; i++) {pthread_mutex_lock(&mymutex);j = myglobal;j = j + 1;printf(".");fflush(stdout);sleep(1);myglobal = j;pthread_mutex_unlock(&mymutex);}return NULL;
}
int main(void) {pthread_t mythread;int i;if (pthread_create(&mythread, NULL, thread_function, NULL)) {printf("error creating thread.");abort();}for (i = 0; i < 5; i++) {pthread_mutex_lock(&mymutex);myglobal = myglobal + 1;pthread_mutex_unlock(&mymutex);printf("o");fflush(stdout);sleep(1);}if (pthread_join(mythread, NULL)) {printf("error joining thread.");abort();}printf("\nmyglobal equals %d\n", myglobal);exit(0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

運行效果

線程私有數據

??在多線程環境里,應避免使用靜態變量。在 Linux 系統中提供 了線程特定數據(TSD)來取代靜態變量。它類似于全局變量,但是,是各個線程私有的, 它以線程為界限。TSD 是定義線程私有數據的惟一方法。同一進程中的所有線程,它們的同 一特定數據項都由一個進程內惟一的關鍵字 KEY 來標志。用這個關鍵字,線程可以存取線程私有數據。 在線程特定數據中通常使用四個函數。

  • pthread_key_create
#include <pthread.h> 
int pthread_key_create(pthread_key_t *key, void (* destructor)(void *value)); 
  • 1
  • 2

??pthread_key_create 函數在進程內部分配一個標志 TSD 的關鍵字。?
??參數 key 指向創建的關 鍵字,該關鍵字對于一個進程中的所有線程是惟一的。所以在創建 key 時,每個進程只能調 用一次創建函數 pthread_key_create。在 key 創建之前,所有線程的關鍵字值是 NULL。一旦 關鍵字被建立,每個線程可以為該關鍵字綁定一個值。這個綁定的值對于線程是惟一的,每 個線程獨立維護。?
?? 參數 destructor 是一個可選的析構函數,可以和每個關鍵字聯系起來。如果一個關鍵字 的 destructor 函數不為空,且線程為該關鍵字綁定了一個非空值,那么在線程退出時,析構函 數將會被調用。對于所有關鍵字的析構函數,執行順序是不能指定的。?
??該函數正常執行后返回值為 0,否則返回錯誤碼。

  • pthread_once
#include <pthread.h> 
int pthread_once(pthread_once_t *once, void (*init) (void)); 
  • 1
  • 2

??pthread_once 函數使用 once 參數所指的變量,保證每個進程只調用一次 init 函數。通常 once 參數取常量 PTHREAD_ONCE_INIT,它保證每個進程只調用一次 init 函數。?
??該函數正常執行后返回值為 0,否則返回錯誤碼。

  • pthread_setspecific
#include <pthread.h> 
int pthread_setspecific(pthread_key_t key, const void *value);
  • 1
  • 2

??pthread_setspecific 函數為 TSD 關鍵字綁定一個與本線程相關的值。?
??參數 key 是 TSD 關 鍵字。?
??參數 value 是與本線程相關的值。value 通常指向動態分配的內存區域。?
??該函數正常執行后返回值為 0,否則返回錯誤碼。

  • pthread_getspecific
#include <pthread.h> 
void * pthread_getspecific(pthread_key_t key); 
  • 1
  • 2

??pthread_getspecific 函數獲取與調用線程相關的 TSD 關鍵字所綁定的值。?
??參數 key 是 TSD 關鍵字。?
??該函數正常執行后返回與調用線程相關的 TSD 關鍵字所綁定的值。否則返回 NULL。

??線程安全性代碼示例:

#include <stdio.h>
#include <pthread.h>
pthread_key_t   key;
void echomsg(int t)
{printf("destructor excuted in thread %d,param=%d\n", pthread_self(), t);
}
void * child1(void *arg)
{int tid = pthread_self();printf("thread1 %d enter\n", tid);pthread_setspecific(key, (void *)tid);sleep(2);printf("thread1 %d key’s %d\n", tid, pthread_getspecific(key));sleep(5);
}
void * child2(void *arg)
{int tid = pthread_self();printf("thread2 %d enter\n", tid);pthread_setspecific(key, (void *)tid);sleep(1);printf("thread2 %d key’s %d\n", tid, pthread_getspecific(key));sleep(5);
}
int main(void)
{pthread_t tid1, tid2;printf("hello\n");pthread_key_create(&key, (void *)echomsg);pthread_create(&tid1, NULL, child1, NULL);pthread_create(&tid2, NULL, child2, NULL);sleep(10);pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_key_delete(key);printf("main thread exit\n");return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

運行結果

小結

??使用多線程實現并發服務器的優點是線程的開銷小,切換容易。但是由于線程共享相同 的內存區域,所以在對共享數據的進行操作時,要注意同步問題。其中線程特定數據雖然實現起來比較煩瑣,但是它是將一個非線程安全 函數轉換成線程安全函數的常用方法。?
??除此之外,還可以通過改變調用函數參變量的方式實現線程的安全性,這里不作介紹。?
??下一篇文章將介紹另外一種實現并發服務器的方法:I/O 多路復用。


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

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

相關文章

leetcode(二)二分法查找算法

給定一個 n 個元素有序的&#xff08;升序&#xff09;整型數組 nums 和一個目標值 target &#xff0c;寫一個函數搜索 nums 中的 target&#xff0c;如果目標值存在返回下標&#xff0c;否則返回 -1。 示例 1: 輸入: nums [-1,0,3,5,9,12], target 9 輸出: 4 解釋: 9 出現在…

leetcode(977)有序數組的平方

給你一個按 非遞減順序 排序的整數數組 nums&#xff0c;返回 每個數字的平方 組成的新數組&#xff0c;要求也按 非遞減順序 排序。 示例 1&#xff1a; 輸入&#xff1a;nums [-4,-1,0,3,10] 輸出&#xff1a;[0,1,9,16,100] 解釋&#xff1a;平方后&#xff0c;數組變為 […

IO多路復用之select全面總結(必看篇)

轉載&#xff1a;http://www.jb51.net/article/101057.htm 1、基本概念 IO多路復用是指內核一旦發現進程指定的一個或者多個IO條件準備讀取&#xff0c;它就通知該進程。IO多路復用適用如下場合&#xff1a; &#xff08;1&#xff09;當客戶處理多個描述字時&#xff08;一般…

leetcode(189) 旋轉數組

**給定一個數組&#xff0c;將數組中的元素向右移動 k 個位置&#xff0c;其中 k 是非負數。 進階&#xff1a; 盡可能想出更多的解決方案&#xff0c;至少有三種不同的方法可以解決這個問題。 你可以使用空間復雜度為 O(1) 的 原地 算法解決這個問題嗎&#xff1f; 示例 1: …

I/O 多路復用之select

轉載&#xff1a;http://blog.csdn.net/u012432778/article/details/47347133 概述 Linux提供了三種 I/O 多路復用方案&#xff1a;select&#xff0c;poll和epoll。在這一篇博客里先討論select, poll 在將下一篇中介紹&#xff0c;epoll是Linux特有的高級解決方案&#xff0c;…

leetcode(283)移動零

283. 移動零 給定一個數組 nums&#xff0c;編寫一個函數將所有 0 移動到數組的末尾&#xff0c;同時保持非零元素的相對順序。 示例: 輸入: [0,1,0,3,12] 輸出: [1,3,12,0,0] 說明: 必須在原數組上操作&#xff0c;不能拷貝額外的數組。 盡量減少操作次數。 方法一&#xff1…

exec函數族實例解析

轉載&#xff1a;http://www.cnblogs.com/blankqdb/archive/2012/08/23/2652386.html fork()函數通過系統調用創建一個與原來進程(父進程)幾乎完全相同的進程(子進程是父進程的副本&#xff0c;它將獲得父進程數據空間、堆、棧等資源的副本。注意&#xff0c;子進程持有的是上述…

leetcode(167)兩數之和 II - 輸入有序數組

兩數之和 II - 輸入有序數組 給定一個已按照 升序排列 的整數數組 numbers &#xff0c;請你從數組中找出兩個數滿足相加之和等于目標數 target 。 函數應該以長度為 2 的整數數組的形式返回這兩個數的下標值。numbers 的下標 從 1 開始計數 &#xff0c;所以答案數組應當滿足 …

常量指針與指針常量的區別(轉帖)

轉載&#xff1a;http://www.cnblogs.com/witty/archive/2012/04/06/2435311.html 三個名詞雖然非常繞嘴&#xff0c;不過說的非常準確。用中國話的語義分析就可以很方便地把三個概念區分開。 一) 常量指針。 常量是形容詞&#xff0c;指針是名詞&#xff0c;以指針為中心的一個…

c/c++錯題總結

1.類名 對象名 默認調用“對象名()”這個構造函數&#xff0c;在棧內存中存在對象名&#xff0c;在堆內存中存在實際對象&#xff1b; 2.類名 對象名(一個或以上個參數) 默認調用相應的構造函數&#xff0c;在棧內存中存在對象名&#xff0c;在堆內存中也是存在實際對象的&a…

智能指針學習筆記

轉載&#xff1a;http://www.cnblogs.com/wuchanming/p/4411878.html 1. 介紹 本文介紹智能指針的使用。智能指針是c 中管理資源的一種方式&#xff0c;用智能指針管理資源&#xff0c;不必擔心資源泄露&#xff0c;將c 程序員 從指針和內存管理中解脫出來&#xff0c;再者&…

c++程序編譯過程

c程序編譯分成四個過程&#xff1a;編譯預處理&#xff0c;編譯&#xff0c;匯編&#xff0c;鏈接 編譯預處理&#xff1a;處理以#為開頭 編譯&#xff1a;將.cpp文件翻譯成.s匯編文件 匯編&#xff1a;將.s匯編文件翻譯成機器指令.o文件 鏈接&#xff1a;匯編生產的目標文件.o…

仿函數(函數對象)

轉載&#xff1a;http://www.cnblogs.com/wuchanming/p/4411867.html 本文乃作者學習《C標準程序庫》的學習筆記&#xff0c;首先介紹了仿函數&#xff08;函數對象&#xff09;和函數適配器&#xff08;配接器&#xff09;的概念&#xff0c;然后列出STL中所有的仿函數&#x…

C++ template —— 動多態與靜多態(六)

轉載&#xff1a;http://www.cnblogs.com/yyxt/p/5157517.html 前面的幾篇博文介紹了模板的基礎知識&#xff0c;并且也深入的講解了模板的特性。接下來的博文中&#xff0c;將會針對模板與設計進行相關的介紹。 ------------------------------------------------------------…

變量之間的區別

全局變量、局部變量、靜態全局變量、靜態局部變量的區別 c變量根據定義具有不同的生命周期&#xff0c;會有不同的作用域&#xff0c;主要有六個作用域&#xff1a;全局作用域&#xff0c;局部作用域&#xff0c;文件作用域&#xff0c;類作用域&#xff0c;語句作用域&#xf…

計算機的網絡體系以及參考模型

計算機的網絡體系以及參考模型一、OSI七層模型二、TCP/IP參考模型三、TCP/IP 五層參考模型四、OSI 模型和 TCP/IP 模型異同比較五、OSI 和 TCP/IP 協議之間的對應關系六、為什么 TCP/IP 去除了表示層和會話層&#xff1f;七、數據如何在各層之間傳輸&#xff08;數據的封裝過程…

C++ 模板詳解(二)

轉載&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/25/2736224.html 四、類模板的默認模板類型形參 1、可以為類模板的類型形參提供默認值&#xff0c;但不能為函數模板的類型形參提供默認值。函數模板和類模板都可以為模板的非類型形參提供默認值。 2、類模板的類…

c++類對象的創建方式

對象創建限制在堆或棧 c類對象的創建方式對象創建限制在堆或棧C 中的類的對象的建立模式如何將類限制在堆上呢&#xff1f;C 中的類的對象的建立模式 C 中的類的對象的建立模式分為兩張&#xff1a;靜態建立&#xff0c;動態建立 靜態建立&#xff1a;由編譯器為對象在棧空間…

C++ 模板詳解(一)

轉載&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/25/2738929.html C模板 模板是C支持參數化多態的工具&#xff0c;使用模板可以使用戶為類或者函數聲明一種一般模式&#xff0c;使得類中的某些數據成員或者成員函數的參數、返回值取得任意類型。 模板是一種對類…

劍指Offer09. 用兩個棧實現隊列

class CQueue { public:stack<int> stack1,stack2;CQueue() {//初始化棧while(!stack1.empty()){stack1.pop();}while(!stack2.empty()){stack2.pop();}}void appendTail(int value) {stack1.push(value);}int deleteHead() {if(stack2.empty()){while(!stack1.empty()){…