問題1、數組和指針的區別
數組名不可以作為左值
char * p1 = "Hello World" ; //分配字符串常量,然后賦給 p1 ,一個指針型變量,是左值 char p2[ 20] = "Hello World" ; //分配一個數組,然后初始化為字符串,相當於一個常量,類型為數組,不是左值 *p1 = 'h' ; //p1可以指向別的地方,但hello world不能更改 p2[ 0] = 'h' ; //p2不能指向別的地方,但hello world可以更改
sizeof運算
sizeof(指針變量p1)是編譯器分配給指針(也就是一個地址)的內存空間。
sizeof(數組指針常量p2)是整個數組占用空間的大小。但當數組作為函數參數進行傳遞時,數組就自動退化為同類型的指針。
取地址&運算
對數組名取地址&運算,得到的還是數組第一個元素的地址
對指針取地址&運算,得到的是指針所在的地址,也就是指向這個指針的指針。因此main函數的參數char *argv[],也可以寫成char **argv。
參考
問題2、指針數組、數組指針與二維數組剖析
定義
指針數組:首先它是一個數組,數組的元素都是指針,數組占多少個字節由數組本身決定。它是“儲存指針的數組”的簡稱。
數組指針:首先它是一個指針,它指向一個數組。在32 位系統下永遠是占4 個字節,至於它指向的數組占多少字節,不知道。它是“指向數組的指針”的簡稱。
實例區分
int *p1[10]; //p1 是數組名,其包含10 個指向int 類型數據的指針,即指針數組 int (*p2)[10]; //p2 是一個指針,它指向一個包含10 個int 類型數據的數組,即數組指針 cout<<sizeof(a)<<" "<<sizeof(b); //4 40
實例分析
符號優先級: ()> [ ] > *
p1 先與“[]”結合,構成一個數組的定義,數組名為p1,int *修飾的是數組的內容,即數組的每個元素。
“*”號和p2 構成一個指針的定義,指針變量名為p2,int 修飾的是數組的內容,即數組的每個元素。數組在這里並沒有名字,是個匿名數組。
數組和指針參數是如何被編譯器修改的?
“數組名被改寫成一個指針參數”規則並不是遞歸定義的。數組的數組會被改寫成“數組的指針”,而不是“指針的指針”:
實參 所匹配的形參 數組的數組 char c[8][10]; char (*)[10]; 數組指針 指針數組 char *c[10]; char **c; 指針的指針 數組指針(行指針) char (*c)[10]; char (*c)[10]; 不改變 指針的指針 char **c; char **c; 不改變
void f(char **c){ cout<<c[1][2]<<endl; } int main(){ char *c[3]={"abc","def","ghi"}; f(c); }
*c[3] 表示這是一個一維數組,數組內的元素是char *類型,每個元素分別指向一個字符串。因為 [] 優先級大於 * 的優先級。
問題3、結構體和數組
數組中存放的是連續的元素,比如int、char等。同樣,可以存放struct元素。
實例
struct UnionFieldInfo { int flag; //字段選中標識: 0 未選中 1 選中 char *field; // 選中字段 char *fieldDetail; //選中字段在數據庫中具體表示 }; struct UnionFieldInfo HACAlarmAudioInfo[] = { {0, "time", "events.time"}, {0, "matchRule", "events.content"}, {0, "level", "events.rule_level"}, {0, "userName", "session.username"}, {-1, NULL, NULL}, }; /* int flag*/ int UnionGetAlarmFieldInit() { int i = 0; for (i = 0; i < sizeof(HACAlarmAudioInfo)/sizeof(HACAlarmAudioInfo[0]); i++) { HACAlarmAudioInfo[i].flag = 0; //init } return 0; } /* get field */ static char * UnionGetAlarmField(char *Field) { int i = 0; for (i = 0; i < sizeof(HACAlarmAudioInfo)/sizeof(HACAlarmAudioInfo[0]); i++) { if(HACAlarmAudioInfo[i].flag == 1) { return HACAlarmAudioInfo[i].field; } } return NULL; } /* make fielddetail sql */ int UnionMakeAlarmFieldSQL(char *sql) { int i = 0; for (i = 0; i < sizeof(HACAlarmAudioInfo)/sizeof(HACAlarmAudioInfo[0]); i++) { if(HACAlarmAudioInfo[i].flag == 1) { sprintf(sql+strlen(sql), "%s, ", HACAlarmAudioInfo[i].fieldDetail); } } /*cancel ", "*/ if(strlen(sql) >= strlen(", ")) { tinysql[strlen(sql)-strlen(", ")] = 0; } }
一維數組HACAlarmAudioInfo中存放的是struct UnionFieldInfo元素。
問題4、帶逗號的字符串分隔
使用strtok進行分隔帶有逗號的字符串
if (strlen(stApplyInfo.userGroupID)) { char *tmpstr = NULL; char s[2] = ","; tmpstr = strtok(stApplyInfo.userGroupID, s); //返回被分解的第一個子字符串,如果沒有可檢索的字符串,則返回一個空指針。 while(tmpstr) { memset(sql, 0, 2048); UnionDBPrintf(sql, 2048, "insert into workOrder_userGroup(workOrder, userGroupID) values('%s','%d')", worknote, atoi(tmpstr)); UnionLogDebugEx("draw.log","sql=[%s]",sql); UnionErr = UnionDBInsert(sql, &row, &errmsg); if (UnionErr) { UnionLogErrEx("draw.log", "db insert failed:[%s]", errmsg); free(sql); return UnionErr; } tmpstr = strtok(NULL, s); } }
問題5、字符串拷貝函數
最好使用memcpy()、snprintf()函數進行字符串拷貝。而strcpy()、strncpy()不太好。
strcpy()容易溢出,只用於字符串的拷貝。
strcpy(p+1, p); //內存重疊
char * strcpy(char * dest, const char * src) // 實現src到dest的復制 { if ((src == NULL) || (dest == NULL)) //判斷參數src和dest的有效性 { return NULL; } char *strdest = dest; //保存目標字符串的首地址 while ((*strDest++ = *strSrc++)!='\0'); //把src字符串的內容復制到dest下 return strdest; }
strncpy()標准用法
strncpy(path, src, sizeof(path) - 1); path[sizeof(path) - 1] = '/0'; len = strlen(path);
memcpy將N個字節的源內存地址的內容拷貝到目標內存地址中。
對於需要復制的內容沒有限制,復制任意內容,例如字符數組、整型、結構體、類等。
當源內存和目標內存存在重疊時,memcpy會出現錯誤。
memcpy(p+1, p, 512); //內存重疊
void* memcpy(void* dest, const void* src, size_t n) { assert((NULL != dest) && (NULL != src)); char* d = (char*) dest; const char* s = (const char*) src; while(n-–) *d++ = *s++; return dest; }
內存重疊
函數strcpy和函數memcpy都沒有對內存重疊做處理的,使用這兩個函數的時候只有程序員自己保證源地址和目標地址不重疊,或者使用memmove函數進行內存拷貝。
memmove函數對內存重疊做了處理。內存重疊時,使用strcpy函數則程序會崩潰。使用memcpy的話程序會等到錯誤的結果。
memmove將N個字節的源內存地址的內容拷貝到目標內存地址中
當源內存和目標內存存在重疊時,能正確地實施拷貝,但這也增加了一點點開銷。
void* memmove(void* dest, const void* src, size_t n) { assert((NULL != dest) && (NULL != src)); char* d = (char*) dest; const char* s = (const char*) src; if (s>d) { // start at beginning of s while (n--) *d++ = *s++; } else if (s<d) { // start at end of s d = d+n-1; s = s+n-1; while (n--) *d-- = *s--; } return dest; //do nothing }
(1)內存低端 <-----s-----> <-----d-----> 內存高端 start at end of s (2)內存低端 <-----s--<==>--d-----> 內存高端 start at end of s (3)內存低端 <-----sd-----> 內存高端 do nothing (4)內存低端 <-----d--<==>--s-----> 內存高端 start at beginning of s (5)內存低端 <-----d-----> <-----s-----> 內存高端 start at beginning of s
(1)當源內存的首地址等於目標內存的首地址時,不進行任何拷貝
(2)當源內存的首地址大於目標內存的首地址時,實行正向拷貝
(3)當源內存的首地址小於目標內存的首地址時,實行反向拷貝
問題6、字符串查找函數
strstr() 函數
char *strstr(const char *haystack, const char *needle) 參數說明: haystack -- 要被檢索的 C 字符串。 needle -- 在 haystack 字符串內要搜索的小字符串。 返回值: 該函數返回在 haystack 中第一次出現 needle 字符串的位置,如果未找到則返回 null。 實例: ret = strstr("RUNOOB", "NOOB"); printf("子字符串是: %s\n", ret); //"NOOB"
代碼實現
#include <cstring> #include <iostream> #include <cassert> using namespace std; char* my_strstr(char* str, char* sub) { assert(str != NULL); assert(sub != NULL); int str_len = strlen(str); int sub_len = strlen(sub); if (str_len < sub_len) /*不用比較,肯定不是*/ { return NULL; } if (str_len != 0 && sub_len == 0) /*aaaaaaaaaaaaaaaaaa, "" ,比較需要花費時間很多*/ { cout << "子串為空。。。" << endl; return NULL; } if (str_len == 0 && sub_len == 0) /*都為空可以直接返回*/ { cout << "原串和子串都為空 !" << endl; return str; } for (int i = 0; i != strlen(str); ++i) { int m = 0, n = i; cout << "原串剩余的長度 : " << strlen(str + i) << endl; cout << "子串的長度 : " << sub_len << endl; if (strlen(str + i) < sub_len) /*往后找如果原串長度不夠了,則肯定不是*/ { cout << "子串太長啦。。。" << endl; return NULL; } if (str[n] == sub[m]) { while (str[n++] == sub[m++]) { if (sub[m] == '\0') { return str + i; } } } } return NULL; }
問題7、const用法
const與指針
const放在*左邊表示指針指向的是一個常量,放在*后面表示這是一個常量指針。
int a=5, b=10; const int* p = &a; //p指向的是一個常量,即p的內容為常量 int const* p = &a; //p指向的是一個常量,即p的內容為常量 p = &b; //可以改變p的指向 //*p = 20; //錯誤:但是不能改變a的內容 int* const p1 = &a; //p1是一個常量指針,表明指針p1是常量不能進行修改,但是p指的內容是可以修改的 *p1 = 30; //p指的內容是可以修改的 //p1=&b; //錯誤:指針p的指向不能修改
const修飾函數參數
const修改函數參數表示參數不可修改,可起到保護的作用。按值傳遞的參數加上const是沒意義的,因為參數傳遞的時候是拷貝的。按引用傳遞時加上const保證傳出參數的同時對象也不會被更改。
void foo(const int a) { // a=10; //錯誤,函數參數不可改變 }
const修改成員函數
const修改成員表示該函數不能修改任何成員變量的值,同時不能調用任何非const修飾的函數,因為沒有const修飾的函數會修改成員變量的值。const修飾函數放在最后面。
#include<iostream> using namespace std; const int a()//返回值為const { return 3; } class A { public: int temp; int fun (int x)const//類中成員函數的函數體由const修飾時,不能改變成員變量的值 { //temp=5;//修改了temp的值,出錯 return 0; } }; int main() { //int v=a();//a()只能作為右值,可以賦給int v和const int v const int v=a(); cout<<v; A a; a.fun(5); return 0; }
const修飾返回值
一、常量可以賦給常量和變量。
二、指向常量的指針必須必須賦給指向常量的指針,指向變量的指針可以賦給指向變量或常量的指針。
const char* foo() { const char a = 'c'; const char* p = &a; return p; } char* const foo2() { char a = 'c'; char* const p = &a; return p; } const char* const foo3() { const char a = 'c'; const char* const p = &a; return p; } int main() { //char* p = foo(); //error,指向常量的指針不能賦給指向變量的指針 const char* p = foo(); //指向常量的指針 const char* const p4 = foo(); //把變量指針賦給常量指針,合法 char* p2 = foo2(); //把常量指針賦給變量指針,合法 char* const p3 = foo2(); //常量指針 const char* p5 = foo2(); //指向變量的指針可以賦給指向常量的指針 const char* const p6 = foo3(); //指向常量的常量指針 const char* p7 = foo3(); //指向常量的變量指針 //char* const p8 = foo3(); //指向變量的常量指針,error //char* p9 = foo3(); //指向變量的變量指針,error cin.get(); return 0; }
問題8、代理中的echo調試記錄
各種代理(ssh等)調試日志,很難獲取。可以使用echo命令,通過以下簡單幾行代碼進行調試。
int ret = 0; char log_cmd[1024] = {0}, buff[1024] = {0}; snprintf(buff, 1024, "%s", "test log by system()"); snprintf(log_cmd, 1024, "echo '%s' >> /tmp/log.txt", buff); ret = system(log_cmd);
問題9、非root帳戶下密鑰認證腳本生成中的用戶主目錄獲取
Linux 命令行下可通過 echo $HOME進行獲取。
//獲取當前用戶主目錄 char *UnionGetRealHomeDir() { char *path = getenv("HOME"); return path; }