C高階編程

### main函數執行之前做了什么?(iOS)

&? dyld 是Apple 的動態鏈接器;在 xnu 內核為程序啟動做好準備后,就會將 PC 控制權交給 dyld 負責剩下的工作 (dyld 是運行在 用戶態的, 這里由 內核態 切到了用戶態)。

1)dyld 開始將程序二進制文件初始化

2)交由ImageLoader 讀取 image,其中包含了我們的類,方法等各種符號(Class、Protocol 、Selector、 IMP)

3)由于runtime 向dyld 綁定了回調,當image加載到內存后,dyld會通知runtime進行處理

4)runtime 接手后調用map_images做解析和處理

5)接下來load_images 中調用call_load_methods方法,遍歷所有加載進來的Class,按繼承層次依次調用Class的+load和其他Category的+load方法

6)至此 所有的信息都被加載到內存中

7)最后dyld調用真正的main函數

注意:dyld會緩存上一次把信息加載內存的緩存,所以第二次比第一次啟動快一點


### KVO實現原理

1.KVO是基于runtime機制實現的

2.當某個類的屬性對象第一次被觀察時,系統就會在運行期動態地創建該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內實現真正的通知機制

3.如果原類為Person,那么生成的派生類名為NSKVONotifying_Person

4.每個類對象中都有一個isa指針指向當前類,當一個類對象的第一次被觀察,那么系統會偷偷將isa指針指向動態生成的派生類,從而在給被監控屬性賦值時執行的是派生類的setter方法

5.鍵值觀察通知依賴于NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey:;在一個被觀察屬性發生改變之前, willChangeValueForKey:一定會被調用,這就 會記錄舊的值。而當改變發生后,didChangeValueForKey:會被調用,繼而 observeValueForKey:ofObject:change:context: 也會被調用。


### ASCII碼表的一般規律

& 16進制的0x30到0x39表示數字0到數字9;

& 16進制的0x61到0x7A表示小寫字母a到z;

& 16進制的0x41到0x5A表示大寫字母A到Z;

記住: jpg的頭部是<ffd8ffe0>? png的頭部是<89504e47>


### TCP的幾種狀態

在TCP層,有個FLAGS字段,這個字段有以下幾個標識:SYN, FIN, ACK, PSH, RST, URG.

其中,對于我們日常的分析有用的就是前面的五個字段。

它們的含義是:

SYN表示建立連接,

FIN表示關閉連接,

ACK表示響應,

PSH表示有 DATA數據傳輸,

RST表示連接重置。


### 信號量

dispatch_semaphore的使用場景是處理并發控制.

dispatch_semaphore_create => 創建一個信號量

dispatch_semaphore_signal => 發送一個信號

dispatch_semaphore_wait => 等待信號

& 系統中規定當信號量值為0時,必須等待,知道信號量值不為零才能繼續操作。?

我們的信號量也可以實現同樣的功能。 首先,創建一個信號量。 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 創建方法里會傳入一個long型的參數,這個東西你可以想象是一個庫存。有了庫存才可以出貨。 dispatch_semaphore_wait,就是每運行一次,會先清一個庫存,如果庫存為0,那么根據傳入的等待時間,決定等待增加庫存的時間,如果設置為DISPATCH_TIME_FOREVER,那么意思就是永久等待增加庫存,否則就永遠不往下面走。

dispatch_semaphore_signal,就是每運行一次,增加一個庫存.

// 某個信號進行等待, timeout:等待時間,永遠等待為 DISPATCH_TIME_FOREVER

dispatch_semaphore_wait(<#dispatch_semaphore_t dsema#>, <#dispatch_time_t timeout#>)

等待信號,具體操作是首先判斷信號量desema是否大于0,如果大于0就減掉1個信號,往下執行;

如果等于0函數就阻塞該線程等待timeout(注意timeout類型為dispatch_time_t)時,其所處線程自動執行其后的語句。


### TCP連接的三次握手

第一次握手:客戶端發送syn包(syn=j)到服務器,并進入SYN_SEND狀態,等待服務器確認;

第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包,即SYN+ACK包,此時服務器進入SYN+RECV狀態;

第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次狀態。


### 字典實現原理

一:字典原理

NSDictionary(字典)是使用hash表來實現key和value之間的映射和存儲的

方法:-?(void)setObject:(id)anObject?forKey:(id)aKey;

Objective-C中的字典NSDictionary底層其實是一個哈希表

二:哈希原理

散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。

給定表M,存在函數f(key),對任意給定的關鍵字值key,代入函數后若能得到包含該關鍵字的記錄在表中的地址,則稱表M為哈希(Hash)表,函數f(key)為哈希(Hash) 函數。

哈希概念:哈希表的本質是一個數組,數組中每一個元素稱為一個箱子(bin),箱子中存放的是鍵值對。

三:哈希存儲過程

1.根據 key 計算出它的哈希值 h。

2.假設箱子的個數為 n,那么這個鍵值對應該放在第 (h % n) 個箱子中。

3.如果該箱子中已經有了鍵值對,就使用開放尋址法或者拉鏈法解決沖突。

在使用拉鏈法解決哈希沖突時,每個箱子其實是一個鏈表,屬于同一個箱子的所有鍵值對都會排列在鏈表中。

哈希表還有一個重要的屬性: 負載因子(load factor),它用來衡量哈希表的空/滿程度,一定程度上也可以體現查詢的效率,計算公式為:

負載因子 = 總鍵值對數 / 箱子個數

負載因子越大,意味著哈希表越滿,越容易導致沖突,性能也就越低。因此,一般來說,當負載因子大于某個常數(可能是 1,或者 0.75 等)時,哈希表將自動擴容。

哈希表在自動擴容時,一般會創建兩倍于原來個數的箱子,因此即使 key 的哈希值不變,對箱子個數取余的結果也會發生改變,因此所有鍵值對的存放位置都有可能發生改變,這個過程也稱為重哈希(rehash)。

哈希表的擴容并不總是能夠有效解決負載因子過大的問題。假設所有 key 的哈希值都一樣,那么即使擴容以后他們的位置也不會變化。雖然負載因子會降低,但實際存儲在每個箱子中的鏈表長度并不發生改變,因此也就不能提高哈希表的查詢性能。

基于以上總結,細心的朋友可能會發現哈希表的兩個問題:

1.如果哈希表中本來箱子就比較多,擴容時需要重新哈希并移動數據,性能影響較大。

2.如果哈希函數設計不合理,哈希表在極端情況下會變成線性表,性能極低。

HashMap 的實例有兩個參數影響其性能:初始容量和加載因子。容量是哈希表中桶的數量,初始容量只是哈希表在創建時的容量。加載因子 是哈希表在其容量自動增加之前可以達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 rehash 操作(即重建內部數據結構),從而哈希表將具有大約兩倍的桶數。

通常,默認加載因子 (.75) 在時間和空間成本上尋求一種折衷。加載因子過高雖然減少了空間開銷,但同時也增加了查詢成本(在大多數 HashMap 類的操作中,包括 get 和 put 操作,都反映了這一點)。在設置初始容量時應該考慮到映射中所需的條目數及其加載因子,以便最大限度地減少 rehash 操作次數。如果初始容量大于最大條目數除以加載因子,則不會發生 rehash 操作。

很多人都有這個疑問,為什么hashmap的數組初始化大小都是2的次方大小時,hashmap的效率最高,我以2的4次方舉例,來解釋一下為什么數組大小為2的冪時hashmap訪問的性能最高。本文主要描述了HashMap的結構,和hashmap中hash函數的實現,以及該實現的特性,同時描述了hashmap中resize帶來性能消耗的根本原因,以及將普通的域模型對象作為key的基本要求。尤其是hash函數的實現,可以說是整個HashMap的精髓所在,只有真正理解了這個hash函數,才可以說對HashMap有了一定的理解。

① hashmap是用鏈地址法進行處理,多個key 對應于表中的一個索引位置的時候進行鏈地址處理,hashmap其實就是一個數組+鏈表的形式。

② 當有多個key的值相同時,hashmap中只保存具有相同key的一個節點,也就是說相同key的節點會進行覆蓋。

③在hashmap中查找一個值,需要兩次定位,先找到元素在數組的位置的鏈表上,然后在鏈表上查找,在HashMap中的第一次定位是由hash值確定的,第二次定位由key和hash值確定。

④節點在找到所在的鏈后,插入鏈中是采用的是頭插法,也就是新節點都插在鏈表的頭部。

⑤在hashmap中上圖左邊綠色的數組中也存放元素,新節點都是放在左邊的table中的,這個在上圖中為了形象的表現鏈表形式而沒有使用。


### 結構體字節大小問題

原則1:數據成員對齊規則:結構(struct或聯合union)的數據成員,第一個數據成員放在offset為0的地方,

后面每個數據成員存儲的起始位置要從該成員(自身)大小的整數倍開始(如int在32位機為4字節,則要從4的整數倍地址開始存儲)。

原則2:結構體作為成員:如果一個結構里有某些結構體成員,則結構體成員要從其內部最大元素大小的整數倍地址開始存儲。

(struct a里存有struct b,b里有char,int,double等元素,那b應該從8的整數倍開始存儲。)

原則3:計算工作:結構體的總大小,也就是sizeof的結果,必須是其內部最大成員的整數倍,不足的要補齊。

例1:struct A{

???????????????????? int a;

???????????????????? double b;

???????????????????? float c;

??????????};

??????????????? struct B{

???????????????????? char e[2];

???????????????????? int f;

???????????????????? double g;

???????????????????? short h;

???????????????????? struct A i;

??????????????????? };

?????? sizeof(A) = 24; int為4,double為8,float為4,總長為8的倍數,補齊,所以整個A為24。

?????? sizeof(B) = 48;


### 函數execve

execve函數調用可以執行一個指定的程序,但一旦執行了execve函數之后,調用execve的進程空間就被指定的程序

占據了.所以execve并不產生新的進程,只是將進程空間替換而已.

char *ss = {"a","123",NULL};//命令行參數

execve("a",ss,NULL);


### 函數wait

父進程調用wait之后會阻塞,直到子進程結束之后才返回,wait函數的參數就是子進程的退出碼.

// fork后的父進程和子進程之間執行是隨機的,無序的,互相不干擾,因為他們是兩個不同的進程.

// 變量的地址為偏移地址,而不是絕對地址.首地址不一樣,修改變量不會影響另一個變量.

// 孤兒進程:子進程活著,父進程死了,這個時候對于子進程來講父進程就變成了init.

// 父進程活著,子進程死了,子進程就成了僵死進程,等父進程收尸。父進程退出的話,僵死的子進程也就沒有了。

// 父進程和子進程會共享打開的文件描述符.


### 函數fork

#include

pid_t fork(void);

// fork調用就是執行自己,內存中會出現一模一樣的兩個進程

// fork執行成功,向父進程返回子進程的pid,子進程內部執行fork返回0<若返回0則說明代碼運行在子進程上>

// fork創建的新進程是和父進程一樣的副本(除了pid不一樣)<變量int m會被克隆到子進程的內存空間,變量是兩份>

// 子進程沒有繼承父進程的超時設置,父進程創建的文件鎖.

### 進程和線程

進程是一個正在執行程序的實例.// 一個PID標識一個進程

程序----就是你磁盤上的那個文件而已,它是靜態的.

進程----一旦這個程序備操作系統加載到內存,開始執行了,那么他就是進程

// 時間片模型


# 倒過來讀,就很容易理解聲明.

int *pt;//指向int型的指針

const int * pci;//指向const int的指針就是指向整數常量的指針

int* const p;// 指向int型變量的常量指針,指針地址不可修改.

void (*foo)(int num);//指向參數為int,返回值為void的函數的指針,函數指針

void *foo(int num);//返回值為指針的函數

int* arr[5];//指針數組,數組中每一個元素都是指針

int (*p)[10] ;// 數組指針,p指向的是一個帶10個int型元素的數組


### 編程高階

-(int)executeWithCommand:(NSString *)cmd {

NSLog(@"%@",cmd);

NSArray *cmds = [cmd componentsSeparatedByString:@" "];

int argc = (int)cmds.count;

char** argv = (char**)malloc(sizeof(char*)*argc);

for(int i = 0;i < argc; i++) {

? ? ? ?argv[i]=(char*)malloc(sizeof(char)*1024);

? ? ? ?strcpy(argv[i],[[cmds objectAtIndex:i] UTF8String]);

}

int ret = ycmagickmain(argc, argv);

for(int i=0;i < argc;i++){

? ? ? free(argv[i]);

? ? ? free(argv);

? ? ? return ret;

}

### 單鏈表 & 二叉樹

typedef struct ListElmt_ {

void *data;

struct ListElmt_ *next;

} ListElmt;

typedef struct BiTreeNode_ {

void *data;

struct BiTreeNode_ *left;

struct BiTreeNode_ *right;

} BiTreeNode;

### memset函數

void *memset(void *s, int ch, size_t n);

函數解釋:將s中當前位置后面的n個字節 (typedef unsigned int size_t )用 ch 替換并返回 s 。

memset:作用是在一段內存塊中填充某個給定的值,它是對較大的結構體或數組進行清零操作的一種最快方法

### memcpy函數

void *memcpy(void *dest, const void *src, size_t n);

從源src所指的內存地址的起始位置開始拷貝n個字節到目標dest所指的內存地址的起始位置中.

函數返回指向dest的指針.

### sprintf函數

功能 把格式化的數據寫入某個字符串緩沖區。

原型 int sprintf( char *buffer, const char *format, [ argument] … );

參數列表

buffer:char型指針,指向將要寫入的字符串的緩沖區。

format:格式化字符串。

[argument]...:可選參數,可以是任何類型的數據。

返回寫入buffer 的字符數,出錯則返回-1. 如果 buffer 或 format 是空指針,且不出錯而繼續,函數將返回-1,并且 errno 會被設置為 EINVAL。

### 文件操作相關函數

函數原型:FILE * fopen(const char * path,const char * mode);

返回值:文件順利打開后,指向該流的文件指針就會被返回。如果文件打開失敗則返回NULL,并把錯誤代碼存在errno中。

一般而言,打開文件后會做一些文件讀取或寫入的動作,若打開文件失敗,接下來的讀寫動作也無法順利進行,所以一般在fopen()后作錯誤判斷及處理。

參數說明:

參數path字符串包含欲打開的文件路徑及文件名,參數mode字符串則代表著流形態。

mode有下列幾種形態字符串:

“r” 以只讀方式打開文件,該文件必須存在。

“r+” 以可讀寫方式打開文件,該文件必須存在。

”rb+“ 讀寫打開一個二進制文件,允許讀寫數據,文件必須存在。

“w” 打開只寫文件,若文件存在則文件長度清為0,即該文件內容會消失。若文件不存在則建立該文件。

“w+” 打開可讀寫文件,若文件存在則文件長度清為零,即該文件內容會消失。若文件不存在則建立該文件。

“a” 以附加的方式打開只寫文件。若文件不存在,則會建立該文件,如果文件存在,寫入的數據會被加到文件尾,即文件原先的內容會被保留。(EOF符保留)

”a+“ 以附加方式打開可讀寫的文件。若文件不存在,則會建立該文件,如果文件存在,寫入的數據會被加到文件尾后,即文件原先的內容會被保留。 (原來的EOF符不保留)

函數原型:int fclose( FILE *fp );

返回值:如果流成功關閉,fclose 返回 0,否則返回EOF(-1)。(如果流為NULL,而且程序可以繼續執行,fclose設定error number給EINVAL,并返回EOF。)

函數原型 size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;

buffer 用于接收數據的內存地址

size 要讀的每個數據項的字節數,單位是字節

count 要讀count個數據項,每個數據項size個字節.

stream 輸入流

返回值

返回真實寫入的項數,若大于count則意味著產生了錯誤。另外,產生錯誤后,文件位置指示器是無法確定的。若其他stream或buffer為空指針,或在unicode模式中寫入的字節數為奇數,此函數設置errno為EINVAL以及返回0.

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);

注意:這個函數以二進制形式對文件進行操作,不局限于文本文件

返回值:返回實際寫入的數據塊數目

(1)buffer:是一個指針,對fwrite來說,是要獲取數據的地址;

(2)size:要寫入內容的單字節數;

(3)count:要進行寫入size字節的數據項的個數;

(4)stream:目標文件指針;

(5)返回實際寫入的數據項個數count。

int fseek(FILE *stream, long offset, int fromwhere);函數設置文件指針stream的位置。

如果執行成功,stream將指向以fromwhere為基準,偏移offset(指針偏移量)個字節的位置,函數返回0。如果執行失敗(比如offset超過文件自身大小),則不改變stream指向的位置,函數返回一個非0值。

/* 讀一個文件,open,close是系統函數,fopen,fclose是庫函數,建議用庫函數 */

int fd = open("a.txt",O_RDONLY);

char buf[100] = {0};

while(read(fd,buf,sizeof(buf)-1) > 0)

{

printf("%s",buf);

memset(buf,0,sizeof(buf));

}

// 寫文件

write(fd,buf,strlen(buf));

close(fd);//記得關閉文件

### 數據庫編程

1:)連接到數據庫

MYSQL *mysql_real_connect(MYSQL *pmvsql,const char* hostname,

const char* username,const char* passwd,const char* dbname,0,0,0);

//函數成功返回指向MySql連接的指針,失敗返回NULL

2:)執行SQL語句的函數

int mysql_query(MYSQL* pmysql,const char* sql);

//成功返回0;

注: 編寫mysql程序,連接到server之后,應該執行sql語句:SET NAMES utf8;

3:)獲取查詢結果

MYSQL_RES* mysql_store_result(MYSQL* pmysql);

// 成功返回一個查詢結果指針,查詢無結果或者錯誤返回NULL

// 需調用mysql_free_result(MYSQL_RES *res) 來釋放相關資源.

4:)查看查詢結果

MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);

// MYSQL_ROW row相當于一個一行數據.

// row[0]表示第一列;

### mysql進階

1:)查詢語句

select [ALL|DISTINCT]結果項列表 from子句 where子句 group子句 having子句 order子句 limit子句

// 上面子句的相對順序不能打亂,實際上,在其內部計算的過程中,也是按此先后順序進行的.

// [all | distinct] 顯示全部重復項(默認) 或 消除重復項

// from 數據來源,表,也可以是表的結合關系

// where 條件

// group by 分組

// having 對分組設定過濾條件

// order by 對前面取得的數據來指定按某個字段的大小進行排序. ASC(正序,默認) DESC(倒序)

// 如果指定多個字段排序,則其含義是,在前一個字段排序中相同的那些數據里,再按后一字段的大小進行排序.

// limit [起始行號 start],[要取出的行數 num] --用于分頁

// 顯示取得第n頁數據: select *from t_name limit ($n-1)*$pageSize,$pageSize

2:)數據庫設計3范式

第一范式(1NF):原子性,數據不可再分

一個表中的數據(字段值)不可再分

第二范式(2NF):唯一性,消除部分依賴

一個表中的每一行必須唯一可區分,且非主鍵字段值完全依賴主鍵字段值

第三范式(3NF):獨立性,消除傳遞依賴

使一個表中的任何一個非主鍵,完全獨立地依賴于主鍵,而不能又依賴于另外的非主鍵

3:)連接查詢

基本形式:

from 表1 [連接方式] join 表2 [on 連接條件];連接的結果可以當作一個“表”來使用。

交叉連接:

from 表1 [cross] join 表2 ;連接的結果其實是兩個表中的所有數據“兩兩對接”。這種連接也叫做“笛卡爾積”

內連接:

from 表1 [inner] join 表2 on 連接條件。inner關鍵字可以省略,也可以用cross代替。on連接條件無非是設定在連接后所得到的數據表中,設定一個條件以取得所需要的數據。通常連接都是指兩個有關聯的表,則連接條件就是這兩個表的關聯字段的一個關系(通常都是相等關系)

左[外]連接:

from 表1 left [outer] join 表2 on 連接條件;將左邊的表的數據跟右邊的表的數據以給定的條件連接,并將左邊的表中無法滿足條件的數據(行)也一并取得——即左邊的表的數據肯定都取出來了

右[外]連接:

from 表1 right [outer] join 表2 on 連接條件;將左邊的表的數據跟右邊的表的數據以給定的條件連接,并將右邊的表中無法滿足條件的數據也一并取得——即右邊的表的數據肯定都取出來了

4:)聯合查詢

含義:將兩個“字段一致”的查詢語句所查詢到的結果以“縱向堆疊”的方式合并到一起,成為一個新的結果集。

形式:

select語句1 union [ALL | DISTINCT] select語句2:

說明:

兩個select語句的查詢結果的字段需要保持一致:個數必須相同,對應順序上的字段類型也應該相同

ALL | DISTINCT表示兩表的數據聯合后是否需要消除相同行(數據)。ALL表示不消除(全部取得),DISTINCT表示要消除。默認不寫就會消除

應該將這個聯合查詢的結果理解為最終也是一個“表格數據”,且默認使用第一個select語句中的字段名

如果第一個select語句中的列有別名,則order by子句中就必須使用該別名

### TCP通信

1:)套接字使用的步驟

初始化 -> 連接 -> 發送(接收)數據 -> 關閉套接字.

2:)函數socket()

int socket(int domain,int type,int protocol);

//protocol一般取0,函數返回值是成功返回套接字描述符,

//失敗返回-1,domain一般取AF_INET,

//type: SOCK_STREAM使用TCP,SOCK_DGRAM使用UDP不可靠連接.

外部依賴: #include

#include

3:)函數bind()

int bind(int sockfd,const struct sockaddr *my_addr,socklen_t addrlen);

// bind將進程與一個套接字聯系起來,通常用于服務器進程為接入客戶連接建立一個套接口;

// sockfd是socket函數調用返回的套接口值,

// my_addr是結構sockaddr的地址

// addrlen設置了my_addr能容納的最大字節數.

4:)函數listen()

int listen(int sockfd,int backlog)

// 服務端調用該函數來監聽指定端口的客戶端連接

// sockfd還是socket標示符

// backlog 最大并發數

5:)函數accept()

int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

// 當有客戶端連接到服務端,它們會排入隊列,直到服務端準備好處理他們為止;

// accept會返回一個新的套接口,同時原來的套接口繼續listen.

// accept會阻塞,直到有客戶端連接.

6:)函數connect()

int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);

// 客戶端調用connect與服務端進行連接.

// $0表示的是客戶端的socket.

7:)函數send() --發送數據

ssize_t send(int s,const void* buf,size_t len,int flags);

// s是已經建立連接的套接口

// buf是要發送數據內存buffer

// len指明buffer的大小

// flags取0.

// 成功,返回發送的字節數,

函數recv與send類似:

ssize_t recv(int s,void *buf,size_t len,int flags);

最后,記得要調用close(int sockfd)來關閉socket;

### epoll函數使用舉例

struct epoll_event ev,events[100];//數組用于回傳要處理的事件

int epfd = epoll_create(100);//可以放100個socket

ev.data.fd = listen_st;//設置與要處理的事件相關的文件描述符

ev.events = EPOLLIN | EPOLLERR | EPOLLHUP(掛起);//設置要處理的事件類型

epol_ctl(epfd,EPOLL_CTL_ADD,listen_st,&ev);//注冊epoll事件

int nfds = epoll_wait(epfd,events,100,-1);//等待epoll事件的發生

### 共享庫so

// so文件在linux為共享庫

// 編譯時gcc需要加-fPIC,使gcc產生與位置無關的代碼[具體函數入口位置由主調進程處理]

// 鏈接時gcc使用-shared選項,指示生成一個.so文件

// 庫文件格式為lib*.so

// 提供一個與該共享庫配套的頭文件[聲明so文件中的函數]

// .bash_profile 中添加 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

// 使得程序鏈接時會在當前目錄下尋找so文件

// gcc -L. -ltest -o hello hello.o [-L.表示在當前目錄尋找so文件,-ltest表示鏈接libtest.so]

// 在.h文件中增加__cplusplus的預編譯指令,就可被C++調用這個庫了

### so文件配套的頭文件舉例

#ifndef TEST_H_

#define TEST_H_

#ifdef __cpluscplus

extern "C"

{

#endif

int max(int a,int b);

int add(int a,int b);

#ifdef __cplusplus

}

#endif

#endif

### 有名管道FIFO

$ mkfifo fifo1? ? # 創建有名管道fifo1

// 有名管道具有持久性

// 有名管道可在任意兩個進程間通信

### 信號

1:)信號通常用來向一個進程通知事件

信號是不可提前預知的,所以信號是異步的.

比如硬件異常,非法內存引用,在鍵盤上按一個鍵等都會發出信號.

ctrl+c --> 發出SIGINT信號

2:)通過fork函數產生的進程與父進程處理信號一樣.

捕捉信號:signal函數

3:)守護進程的創建

- 父進程中執行fork后,執行exit退出

- 在子進程中調用setsid.

- 讓根目錄/ 成為子進程的工作目錄[會調用chdir函數]

- 把子進程的umask設為0 [ umask(0) ]

- 關閉任何不需要的文件描述符

//與守護進程通信,則需要向守護進程發信號

### Shell腳本

1:)shell腳本舉例

#!/bin/sh

WHOAMI='whoami'

PID= `ps -u $WHOAMI | grep signd | awd '{print $1}'`

if (test "$PID" = "") then

./signd

fi

2:)

### man命令的使用

// atoi函數需要引入什么頭文件,不用去死記, $man atoi 查看一下就知道了

// 比如gcc命令的使用 ,$man gcc

### 棋盤坐標的計算

* 棋盤坐標的計算首先要搞清楚棋盤坐標原點在屏幕坐標系中的坐標是多少,

* 再一個需要弄清楚棋盤上落點之間的間隔是多少;最終計算出每個棋子的屏幕坐標;

* 棋子的坐標 = 棋子在棋盤的單位坐標*棋子的直徑+棋盤坐標系相對于屏幕坐標系的偏移量.

* 因為在表結構中紅棋的id始終在前邊,所以不管紅棋在下邊還是黑棋在下邊,都是先擺紅棋,再擺黑棋.

### 判斷是否點擊了某個象棋

void Scene::ccTouchEnded(CCTouch *pTouch,CCEvent *e)

{

CCPoint ptClickUp = pTouch->getLocation();//獲取手指離開屏幕的瞬時坐標

if(_red->boundingBox().containsPoint(ptClickUp)){

//表示精靈被點擊

this->_redSprClicked = true;

}

}

### 象棋的碰撞檢測

scheduleUpdate();//啟動定時器,會每一幀調用update方法

void udpate(float delta){

//精靈默認的錨點是在它的中心

//一般情況下,是判斷兩個精靈的矩形是否有相交的部分,因為這里有旋轉,所以不能用這個.

//象棋是圓形,只需判斷兩個圓的圓心距是否小于r1+r2

float x1 = m_sp1->getPositionX();

float x2 = m_sp2->getPositionY();

if(abs(x1-x2)getContentSize().width*0.5

//兩個精靈發生碰撞

//開始游戲,進入主場景

CCDirector::sharedDirector->replaceScene(SceneGame::scene());

}

}

=====================================================================================

/* 函數 */

### memset函數

原型:extern void *memset(void *buffer, char c, int count);

用法:#include

功能:把buffer所指內存區域的前count個字節設置成字符c。

說明:返回指向buffer的指針

### strcpy函數實現

char *strcpy(char *strDest, const char *strSrc)

{

assert((strDest!=NULL) && (strSrc !=NULL)); // 2分

char *address = strDest; // 2分

while( (*strDest++ = * strSrc++) != ‘\0’ ) // 2分

NULL ;

return address ; // 2分

}

### inet_addr函數

/* Convert Internet host address from numbers-and-dots notation in CP

into binary data in network byte order.? */

extern in_addr_t inet_addr (const char *__cp) __THROW;

eg: inet_addr("192.168.1.23");

### setsockopt函數

int setsockopt(int s,int level,int optname,const void* optval,socklen_t optlen);

// 設置套接口,SO_REUSEADDR指示系統地址可重用.

int on = 1;

setsockopt(st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));

### read函數

read(STDIN_FILENO,s,sizeof(s));//從鍵盤讀取字符串,并緩存到s中

write(STDOUT_FILENO,buf,strlen(buf));//向控制臺寫數據

### fcntl函數

int fcntl(int fd,int cmd,.../* arg */);

//該函數可以將文件或socket描述符設置為阻塞或非阻塞狀態

//fd是要設置的文件描述符或socket

//cmd F_GETFL為得到目前狀態,F_SETFL為設置狀態

//宏定義0_NOBLOCK表示非阻塞,0代表阻塞

//返回值為描述符當前狀態

### epoll_*函數

epoll相當于一個游泳池.

epoll_create() --用來創建一個epoll文件描述符;需要調用close()來關閉epoll句柄.

epoll_ctl() --用來修改需要偵聽的文件描述符或事件;

epoll_wait() --接收發生在被偵聽的描述符上的,用戶感興趣的IO事件;

int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

// epfd是epoll_create的返回值

// op EPOLL_CTL_ADD:注冊新的fd到epfd中

// fd是socket描述符

// event 通知內核需要監聽什么事件

int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);

// epfd是epoll_create的返回值

// epoll_events里面將存儲所有的讀寫事件

// maxevents是當前需要監聽的所有socket句柄數

// timeout -1表示一直等下去

### fork函數

#include

pid_t fork(void);//進程克隆

fork執行成功,向父進程返回子進程的pid,并向子進程返回0,函數返回0表示的是子進程;

fork創建的新進程是和父進程一樣的副本.(除了PID和PPID);

提示: 獲取pid的方法 --> getpid(),getppid()

### execve函數

int execve(const char* path,const char *arg,char * const envp[]);

fork創建了一個新的進程,產生一個新的PID;

execve用被執行的程序完全替換了調用進程的映像;

execve啟動一個新程序,替換原有進程,所以被執行的進程pid不變;

path? --? 要執行的文件完整路徑

arg? ? --? 傳遞給程序完整參數列表

envp? --? 指向執行execed程序的環境指針,可以設為NULL

### getcwd函數

char* getcwd(char* buf,size_t size);

該函數把當前工作目錄的絕對路徑名復制到buf中,size指示buf的大小.

### opendir函數

DIR *opendir(const char* pathname);//打開pathname指向的目錄文件

struct dirent *readdir(DIR *dir);//讀出目錄文件內容

int closedir(DIR *dir);

### getlogin函數

getlogin()函數返回程序的用戶名;

struct passwd* getpwnam(const char* name);//返回/etc/passwd文件中與該登錄名相應的一行完整信息

### system函數

int system(const char* cmd);

//該函數傳遞給/bin/.sh/cmd中可以包含選項和參數

//如果沒有找到/bin/sh 函數返回127,

### wait函數

pid_t wait(int *status);//阻塞調用,直到子進程退出,wait才返回

pid_t waitpid(pid_t pid,int *status,int options);

// wait和waitpid函數收集子進程的退出狀態

// status保存子進程的退出狀態

// pid為等待進程的pid

// 父進程沒有調用wait函數,子進程就退出了,這個時候子進程就成了僵死進程

### exit函數

int exit(int status);

// 導致進程正常終止,并且返回給父進程的狀態

// 無論進程為何終止,最后都執行相同的代碼,關閉文件,釋放內存資源

// void abort()函數會導致程序異常終止

### kill函數

int kill(pid_t pid,int sig);

// kill函數殺死一個進程,由pid指定

// sig表示信號

### pipe函數

int pipe(int filedes[2]);

// 如果成功建立了管道,則會打開兩個文件描述符,一個用于讀數據,一個用于寫數據

// 關閉管道用close函數

### mkfifo函數

int mkfifo(const char* pathname,mode_t mode);

// 創建fifo,函數執行成功返回0

// pathname代表fifo的名稱

// mode表示讀寫權限,比如777

// unlink(const char*) 函數刪除fifo

### shmget函數

int shmget(key_t key,size_t size,int shm_flg);

// 創建共享內存區

// 參數key既可以是IPC_PRIVATE,也可以是ftok函數返回的一個關鍵字

// size指定段的大小,shm_flg表示權限0xxx

// 函數成功則返回段標示符

// 兩個進程要共享一塊內存,實際上是一塊內存分別映射到兩個進程,由操作系統對這塊內存進行同步

// $ipcs -m #查看共享內存

### shmat函數

void* shmat(int shmid,const void* shmaddr,int shmflg);

// 附加共享內存區

// shmid為要附加的共享內存區標示符

// shmaddr一般置為0,由系統分配地址

// shmflg可以是SHM_RDONLY只讀

// 函數成功則返回被附加了段的地址

// 函數shmdt(const void* shmaddr)是將附加在shmaddr的段從調用進程的地址空間分離出去

### signal函數

signal(int signo,void (*func))

// signo 信號名

// func? 回調函數的名稱

int sigaction(int signo,const struct sigaction *act,struct sigaction *oact)

該函數是signal的升級版,會檢查或修改與指定信號相關聯的處理動作

### raise函數

int raise(int signo);

// 發送信號,raise函數一般用于進程向自身發送信號.

unsigned int alarm(unsigned int seconds);

// 設置定時器,當時間到了則發出SIGALARM信號

### sleep函數

unsigned int sleep(unsigned int seconds);

// seconds指定睡眠時間

// 到時間后函數返回.

### openlog函數

void openlog(const char* ident,int option,int facility);//打開日志

void syslog(int priority,const char* format,...);//寫入日志

void closelog();//關閉日志

### pthread_create函數

int pthread_create(pthread_t *thread,const pthread_attr_t *attr,

void* (*start_routine)(void*),void *arg)

// 線程創建函數,[在進程中只有一個控制線程,主線程return,整個進程就終止]

// thread是新線程的ID,

// attr_t 線程屬性

// start_routine回調函數

// arg是線程啟動的附加參數

// 函數成功則返回0

// gcc鏈接時要加-lpthread

注: 多個線程創建后線程的運行順序具有隨機性;

### pthread_exit函數

void pthread_exit(void* arg);

// 單個線程退出函數

// 任一線程調用exit函數,整個進程就會終止

// arg會被其他線程調用,比如pthread_join捕捉

// 線程中不要用信號來處理邏輯,這樣會使得程序變得很復雜

### pthread_join函數

int pthread_join(pthread_t th,void** thr_return);

// 該函數用于掛起當前線程,直至th指定的線程終止才返回

// 如果另一個線程返回值不為空則保存在thr_return中

### pthread_detach函數

int pthread_detach(pthread_t th);

// 使線程處于被分離狀態

// 對于被分離狀態的線程,調用pthread_join無效

// 如果不等待一個線程,同時對該線程的返回值不感興趣,可以設置為該線程為被分離狀態

// 自己不能使自己成為分離狀態,只能由其他線程調用pthread_detach

### pthread_cancel函數

// pthread_cancel函數允許一個線程取消th指定的另一個線程

// 函數成功則返回0

// pthread_equal函數判斷兩個線程是否是同一個線程

### pthread_attr_init函數

int pthread_attr_init(pthread_attr_t *attr);

// pthread_attr_init函數初始化attr結構[線程屬性]

// pthread_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);#線程創建時則為被分離狀態

// pthread_attr_destroy函數釋放attr內存空間

=========================================================================

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容