C/C++基礎知識點面試題
目錄
一、虛擬函式的資料結構,如何工作?
虛擬函式:用virtual定義的函式為虛擬函式。
虛擬函式來源:基於C++的一個特性:子類能轉換成父類,例如:
CBasic *parent;
CBasic *p1;
CChildren *child;
parent = new CBsic;
child = new CChildren;
p1 = new CChildren;
如上程式碼,p1為CBasic型指標,但實際物件是CChildren型,如果子類和父類有相同的函式時,是呼叫子類的函式還是父類的函式?基於這個問題,C++提出多型的概念:根據實際物件型別決定函式呼叫的具體目標,使用virtual關鍵字對多型進行支援。被virtual宣告的函式被重寫後具有多型性。
底層機制:虛擬函式是使用虛擬函式表和虛擬函式表指標實現的。虛擬函式表是一個類虛擬函式的地址,用於索引類本身和類虛擬函式,若子類重寫父類函式,則會在相應的虛擬函式表處替換成子類虛擬函式的地址。虛擬函式表指標存在於每一個物件中,它指向物件所對應類的虛擬函式地址。
建構函式是不是虛擬函式並無多大影響,因為在構造子類一定要先構造父類。在存在繼承並且解構函式需要用來析構資源時,解構函式一定要為虛擬函式,若使用父類指標指向子類,用delete解構函式時,只會呼叫父類解構函式,不會呼叫子類的解構函式,造成記憶體洩漏。
二、const與define的區別?
1、編譯器處理方式:const:編譯時確定其zhi;define:預處理時進行替換
2、型別檢查:const:有資料型別,編譯時進行資料檢查;define:無型別,也不做型別檢查
3、記憶體空間:const:在靜態儲存區儲存,僅此一份;define:在程式碼段區,每做一次替換就會進行一次拷貝
4、define可以用來防止重複定義,const不行
三、指標與引用的區別?
1、指標:一個變數,儲存的內容為一個地址;引用:給一個已有物件起的別名
2、指標是一個實體,需要分配記憶體空間;引用知識變數別名,不需要分配記憶體空間
3、可以有多級指標,不能有多級引用
4、自增運算結果不一樣
5、指標是間接訪問,引用是直接訪問
6、指標可以不用初始化,引用一定要先初始化
四、指標與資料的區別?
1、含以上的區別:陣列對應著一塊記憶體,而指標是指向一塊記憶體。陣列的地址和空間大小在生命週期不會發生改變,內容可能會發生改變,而指標指向的記憶體大小可以隨時發生改變。當指標指向常量字串時,它的內容不可以改。
2、計算容量的區別:用sizeof計算出陣列的元素個數,無法計算指標所指向記憶體的大小
3、陣列名是常量指標,指標是變數指標
4、對陣列用&和對指標&的意義不同,此時陣列名不在當成指向一個元素的常量指標來使用
五、不用臨時變數實現兩個變數的交換
#include<iostream>
using namespace std;
void Switch(int *p1,int *p2)
{
*p1 = *p1 + *p2;
*p2 = *p1 - *p2;
*p1 = *p1 - *p2;
}
void Xor(int *p1, int *p2)
{
*p1 = *p1^*p2; //異或操作
*p2 = *p1^*p2;
*p1 = *p1^*p2;
}
int main()
{
int a = 1, b = 2;
int *p1 = &a;
int *p2 = &b;
cout << "*p1 = " << *p1 << endl;
cout << "*p2 = " << *p2 << endl;
Switch(p1, p2);
cout << "*p1 = " << *p1 << endl;
cout << "*p2 = " << *p2 << endl;
Xor(p1,p2);
cout << "*p1 = " << *p1 << endl;
cout << "*p2 = " << *p2 << endl;
system("pause");
return 0;
}
方法一缺陷:相加和可能存在溢位情況。
六、函式指標與指標函式
函式指標:顧名思義,與整型指標類似,整型指標為指向整型的指標,函式指標為指向函式的指標,是指標變數,他與函式名無掛,只與引數列表和返回型別有關;
指標函式:本質是函式,返回值為一個指標。
#include<iostream>
using namespace std;
int ADD(int a, int b)//求和函式
{
return a + b;
}
int Sub(int a, int b)//做差函式
{
return a - b;
}
int* add(int *p1, int *p2)//求和函式
{
int a = *p1 + *p2;
int *p = &a;
return p;
}
int main()
{
int a = 5, b = 3;
int *p1 = &a;
int *p2 = &b;
//宣告一個函式指標,只能指向兩個整型引數,返回值為Int型的函式
int (*hanshuzhizhen)(int,int);
hanshuzhizhen = ADD; //函式指標初始化,使函式指標指向ADD函式;
cout <<"函式指標指向ADD函式計算結果:" <<hanshuzhizhen(a, b) << endl;
hanshuzhizhen = Sub; //函式指向Sub函式
cout << "函式指標指向Sub函式計算結果:" << hanshuzhizhen(a, b) << endl;
//
cout << " 指標函式計算結果:" <<*add(p1, p2) << endl;
system("pause");
return 0;
}
七、一個C++原始檔從文字到可執行檔案經歷的過程
1、預處理:對所有的define進行巨集替換;處理所有的條件編譯#idef等;處理#include指令;刪除註釋等;bao#pragma
2、編譯:將預處理後的檔案進行詞法分析、語法分析、語義分析以及優化相應的彙編檔案
3、優化:
4、彙編:將彙編檔案轉換成機器能執行的程式碼
5、連結:包括地址和空間分配,符號決議和重定位
八、C++11新特性
1、nullptr代替NULL,傳統C++在識別NULL有兩種情況,一種是空指標,一種是當做0,在過載時往往把應該看成指標的當做0處理
2、型別推導:auto 和decltype(可以讓編譯器找出表示式的型別)
3、區間迭代,使c++的for語句能向python一樣便捷
4、初始化列表
5、模板增強
6、新增容器 :std::array,std::forward_list(單鏈表)
7、正則表示式
8、執行緒支援
9、右值引用(重點)
九、C++和C的不同
1、c語言是面向過程的程式設計,主要核心為:資料結構和演算法,具有高效的特性。對於C語言程式的設計,主要是考慮如何通過一個過程,對輸入進行處理得出一個輸出。C++是面向物件的程式設計,對於C++,首先考慮的是如何構造一個物件模型,讓這個模型配合對應問題,這樣可以通過獲取物件狀態資訊得到輸出
2、C++比C語言的增強點:1、名稱空間2、實用性加強3、register關鍵字4、變數檢測加強5、struct 加強
十、malloc的原理
函式原型: void* malloc(size_t n)返回值額型別為void*,為動態分配得到的記憶體,但代大小是確定的,不允許越界使用。
malloc函式的實質體現在它有一個可以將可用記憶體塊連線成一個長的列表的空閒連結串列,當呼叫連結串列時,它沿著連線表尋找一個大到可以滿足使用者請求所需記憶體,將記憶體一分為二,將分配給使用者那塊記憶體傳給使用者,剩下的那塊返回連線表。
十一、記憶體洩漏、野指標
記憶體洩漏:動態申請的記憶體空間沒有被正常釋放,但也不能繼續被使用的情況
野指標:指向被釋放的記憶體或者訪問受限的指標 造成的原因:1、指標未被初始化 2、被釋放的指標沒有被置為NULL 3、指標越界操作
解決記憶體洩漏的辦法:使用智慧指標
十二、static
1、區域性靜態變數:static區域性變數和普通區域性變數有什麼區別?
static區域性變數和普通區域性變數有什麼區別:static區域性變數只被初始化一次,下一次依據上一次結果值;程式的區域性變數存在於(堆疊)中,全域性變數存在於(靜態區 )中,動態申請資料存在於( 堆)中。
2、全域性靜態變數:static全域性變數與普通的全域性變數有什麼區別?
答 、全域性變數(外部變數)的說明之前再冠以static 就構成了靜態的全域性變數。全域性變數本身就是靜態儲存方式, 靜態全域性變數當然也是靜態儲存方式。 這兩者在儲存方式上並無不同。這兩者的區別雖在於非靜態全域性變數的作用域是整個源程式, 當一個源程式由多個原始檔組成時,非靜態的全域性變數在各個原始檔中都是有效的。 而靜態全域性變數則限制了其作用域, 即只在定義該變數的原始檔內有效, 在同一源程式的其它原始檔中不能使用它。由於靜態全域性變數的作用域侷限於一個原始檔內,只能為該原始檔內的函式公用, 因此可以避免在其它原始檔中引起錯誤。
從以上分析可以看出, 把區域性變數改變為靜態變數後是改變了它的儲存方式即改變了它的生存期。把全域性變數改變為靜態變數後是改變了它的作用域, 限制了它的使用範圍。static全域性變數與普通的全域性變數有什麼區別:static全域性變數只初使化一次,防止在其他檔案單元中被引用;
3、靜態成員函式:static函式與普通函式有什麼區別?
static函式與普通函式作用域不同,僅在本檔案。只在當前原始檔中使用的函式應該說明為內部函式(static),內部函式應該在當前原始檔中說明和定義。對於可在當前原始檔以外使用的函式,應該在一個頭檔案中說明,要使用這些函式的原始檔要包含這個標頭檔案,static函式與普通函式有什麼區別:static函式在記憶體中只有一份,普通函式在每個被呼叫中維持一份拷貝
十三、union和struct
1、在儲存資訊時,struct可以儲存多個成員,而union每個成員會共享一個儲存空間,只能儲存最後一個成員
2、在任何時刻,union只存放被選中的那個成員,struct每個成員都在
3、對union的不同成員賦值,將會對其他成員重寫
十四、new與malloc的區別
1、屬性:new為關鍵字,malloc為庫函式,需要標頭檔案支援
2、引數:使用new申請記憶體無需指定記憶體大小,編譯器會自行計算,而malloc需要顯示的給出所需記憶體的大小
3、返回型別:new分配成功返回的是物件型別指標,與物件嚴格匹配,無需型別轉換,故new是符合型別安全性操作符,malloc返回的是void*
4、分配失敗:new分配失敗,丟擲bad_alloc異常,malloc則是返回NULL
5、過載
6、記憶體區域:new分配的記憶體在自由儲存區,malloc在堆上分配記憶體
十五、C++型別轉換
1、static_cast
2、dynamic_cast
3、const_cats
4、reinterpret_cast
#include<iostream>
//using namespace std;
//static_cast<typeid>()其中typeid可以為一般型別,也可以為指標引用
class A
{
public:
A() :i(1), j(1) {}
~A() {}
void printA()
{
std::cout << "call printA() in class A" << std::endl;
}
void printSum()
{
std::cout << "Sum = " << i + j << std::endl;
}
private:
int i, j;
};
class B :public A
{
public:
B():a(2),b(3){}
~B(){}
void printB()
{
std::cout << "call printB() in class B" << std::endl;
}
void printSum()
{
std::cout << "Sum = " << a + b << std::endl;
}
void Add()
{
a++;
b++;
}
private:
double a, b;
};
int main1()
{
B *ptrB = new B; //建立一個B型別物件。堆區
ptrB->printSum(); //輸出和
A *ptrA = static_cast<A*>(ptrB); //將派生類轉化成父類,上行轉換
ptrA->printA();
ptrA->printSum();
ptrA = new A; //建立一個父類物件
ptrB = static_cast<B*>(ptrA); //將父類物件轉換成子類物件,下行轉換
ptrB->printB();
ptrB->printSum();
B b; //棧上建立一個B型別物件
B &rB = b; //對b的引用
rB.printSum();
A &rA = static_cast<A &>(b); //派生類轉換成基類,上行轉換
rA.printA();
rA.printSum();
A a;
A &rA1 = a;
rA1.printA();
B &rB1 = static_cast<B &>(a); //將基類轉換成派生類,下行轉換
rB1.printB();
rB1.printSum();
system("pause");
return 0;
}
十六、面向物件的瞭解
面向物件:把資料和對資料的操作方法放在一起,做成一個相互依靠的整體,稱之為物件,對同類物件抽象出共同特性,類中大多數資料只能用本類的方法進行處理
面向物件的三大特性:封裝,繼承,多型
封裝:將一類事物的屬性和行為抽象為一個類,使屬性私有化,行為公開化,提高資料隱蔽性,複用性高
繼承:進一步將屬性和行為抽象為一個父類,而每一個子類擁有父類的行為和屬性,也有自己的行為和屬性
多型:介面複用
十七、前置和後置的區別
前置++的實現比較高效,自增之後,將*this指標直接返回即可,一定要返回this指標
後置++的實現比較麻煩,因為要返回自增之前的物件,所以先將物件進行拷貝一份,再進行自增,最後返回那個拷貝
十八、靜態庫和動態庫
靜態庫:1、連結時將程式放進可執行的程式中
2、可產生多個副本
3、不依賴程式執行
動態庫:1、程式執行時,載入時才會到動態庫找函式
2、多執行緒共享
3、依賴程式執行
十九、struct記憶體大小的確定
存在記憶體對齊的緣故,對於32位機器,是4位元組對齊,64位機器是8位元組對齊。
struct A
{
int a;
char b;
int c;
}
如上程式碼:4 + 4 + 4 = 12位元組
二十、strlen,strcpy函式的實現
#include<iostream>
#include"test.h"
using namespace std;
//將src字串複製到desc中
char* mystrcpy(char* desc, const char* src)
{
if (desc == NULL || src == NULL) //記憶體正常判斷
{
return NULL;
}
char* str = desc;
while ( (*str = *src) != '\0')//‘\0'字串結束標誌
{
str++;
src++;
}
return desc;
}
//將src字串連線到desc中
char* mystrcat(char* desc, const char* src)
{
if (desc == NULL || src == NULL)
{
return NULL;
}
char* str = desc;
while (*src++ != '\0'); // 第一步:找出目的字串的結尾
src--;
while ((*desc++ = *src++) != '\0');//第二步:將源字串新增到目的字串
return str;
}
//將src字串前n個字元複製到desc中
char* mystrncpy1(char* desc, const char* src, int n)
{
char* str = desc;
//前面應該有個目的記憶體是否不足判斷
int m = 0;
while (*str != '\0')
{
str++;
m++;
}
if (desc == NULL || src == NULL || n>m ) //記憶體正常判斷
{
return NULL;
}
while (n--)
{
*str++ = *src++;
}
return desc;
}
//比較兩個字串大小
int mystrcmp(const char* str1, const char* str2)
{
while (*str1 && *str2 && *str1 == *str2)
{
str1++;
str2++;
}
return *str1 - *str2;
}
//求出字串str長度
int mystrlength(const char* src)
{
if (src == NULL)
{
return 0;
}
int n = 0;
while (*src++ != '\0')
{
n++;
}
return n;
}
二十一、memcpy,memset內部函式實現
#include<iostream>
using namespace std;
/*
memcpy函式用於資源記憶體(src指向的記憶體)拷貝到目標記憶體(desc指向的記憶體);拷貝的個數size
用法::(1)可以拷貝任何型別的物件,因為函式的引數型別是void* ,由於函式拷貝是一個位元組一個
位元組拷貝,實際操作是將void*強制轉換成了char*,這樣才能保證每一次加一個指標
*/
//實現,未考慮記憶體重疊情況
void* memcpy1(void * desc, const void * src, size_t size)
{
if (desc == NULL && src == NULL)
{
return NULL;
}
unsigned char* desc1 = (unsigned char*)desc; //將void*轉成unsigned char*型別
unsigned char* src1 = (unsigned char*)src; //將void*轉成unsigned char*型別
while (size-->0)
{
*desc1++ = *src1++;
}
return desc;
}
//考慮記憶體重疊的情況
void* memcpy2(void* desc, const void * src, size_t size)
{
if(desc == NULL && src == NULL)
{
return NULL;
}
unsigned char* desc1 = (unsigned char*)desc;
unsigned char* src1 = (unsigned char*)src;
//當記憶體重疊時,從後往前複製
if(desc > src && desc1 < (src1 + size))//記憶體發生重疊
{
for (int i = size - 1; i >= 0; i--)
{
*desc1++ = *src1++;
}
}
else
{
for (int i = 0; i < size; i++)
{
*desc1++ = *src1++;
}
}
return desc;
}
//memset內部實現memset(void*s,int ch,size_t n)
/*將s所指向的某一塊記憶體中的前n個位元組的內容全部設定為ch指定的ASCII值,返回s*/
void * memset1(void * ptr, int value, size_t n)
{
if (ptr == NULL && n < 0)
{
cout << "賦值出現錯誤" << endl;
return;
}
char *s = (char*)ptr; //定義一個指標來接str;輔助指標
while (n--)
{
*s++ = (char)value;
}
return ptr;
}
下面就是根據原始碼而來的memset實現:
void* memset(void* dst,int val, size_t count)
{
void* ret = dst;
while(count--)
{
*(char*)dst = (char)val;
dst = (char*)dst + 1;
}
return ret;
}
二十二、C/C++記憶體管理方式,記憶體分配
記憶體分配方式:在C++中記憶體分為5個區,分別是堆、棧、自由儲存區、全域性/靜態儲存區、常量儲存區。
棧:在執行程式過程中,區域性作用域出現的一些區域性變數可以在棧上建立,等脫離該作用域建立的記憶體被釋放。棧是機器系統提供的資料結構,計算機會在底層對棧提供支援:分配專門的暫存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。
堆:用於程式記憶體動態分配,用c/c++中的new/malloc分配,delete/free釋放。堆則是 C/C++ 函式庫提供的,它的機制是很複雜的。
自由儲存區:它是C++基於new操作符的一個概念,凡是通過new操作符申請的記憶體即為自由儲存區
全域性/靜態儲存區:這塊記憶體在程式編譯期間已經分配好,在程式整個執行階段一直存在。全域性變數個和靜態變數
常量儲存區:特殊的一塊記憶體,裡面存放的是常量,不允許修改。
二十三、深拷貝和淺拷貝
淺拷貝:只是拷貝了基本型別的資料,而引型別資料,複製後也是會發生引用,淺拷貝只是指向被複制的記憶體地址,如果原來物件唄修改,那麼淺拷貝出來的物件也會被修改。
深拷貝:在計算機中開闢一塊新記憶體用於存放複製的物件。因此要用new或者malloc等。
二十四、debug和release的區別
debug:通常稱為除錯版本,包含著除錯資訊,便於程式設計師除錯
release:稱為釋出版本,它往往是經過各種優化,使得程式在程式碼大小和執行速度是最優的,方便使用者使用。
二十五、main是否需要返回值?
main 函式的返回值用於說明程式的退出狀態。如果返回 0,則代表程式正常退出,否則代表程式異常退出。有些編譯器在main函式中沒有新增return語句,但還是能正常編譯通過是因為編譯器要自動在生成的目標檔案中新增return 0,但還是建議加上,提高程式的移植性。
二十六、C++動態連結庫與C動態連結庫
C呼叫C++動態連結庫,需要把C++動態連結庫用C++動態庫提供的類再進行API封裝
C++呼叫C動態連結庫,需要加上extern C,用C編譯準則來編譯
二十七、結構體
結構體比較:C++:過載==,但是要注意記憶體對齊,為了提高CPU的讀取記憶體的效率更高。改變記憶體對齊方法:巨集定義
C語言中不能比較,記憶體對齊,會有垃圾填充,造成干擾。
二十八、拷貝建構函式為什麼傳引用?
原因:引數為引用,不為值傳遞是為了防止拷貝建構函式的無限遞迴,最終導致棧溢位。這也是編譯器的強制要求。
#include<iostream>
class Example
{
private:
int m_test;
public:
//帶引數的建構函式
Example(int x) :m_test(x)
{
std::cout << "constructor with argument!" << std::endl;
}
//拷貝建構函式
Example(const Example &test)
{
m_test = test.m_test;
std::cout << "copy constructor" << std::endl;
}
//賦值運算子過載
Example& operator=(const Example &test)
{
std::cout << "assignment operator" << std::endl;
m_test = test.m_test;
return *this;
}
void Print(Example test)
{
}
};
int main()
{
Example aaa(2);
Example bbb(3);
bbb = aaa;
Example ccc = aaa;
Example ddd(aaa);
bbb.Print(aaa);
system("pause");
return 0;
}
constructor with argument! // Example aaa(2);
constructor with argument! // Example bbb(3);
assignment operator // bbb = aaa;
copy constructor // Example ccc = aaa;
copy constructor // Example ddd(aaa);
copy constructor // bbb.Print(aaa);
第一二就不解釋了
至於第三個,bbb是已經例項化的物件,不需要構造,因此只會呼叫=運算子過載函式,第四個的ccc還沒有例項化,因此要呼叫拷貝建構函式,構造出ccc,而不是=運算子過載函式
第五個就是拷貝建構函式的應用,
第六個:實際是將aaa作為引數傳遞給bbb.Print(Example ex),即Example ex = aaa,和第四個一致,所以還是呼叫拷貝建構函式。
通過這個例子, 我們來分析一下為什麼拷貝建構函式的引數只能使用引用型別。
看第四個輸出: copy constructor //CExample ccc = aaa;
構造ccc,實質上是ccc.CExample(aaa); 我們假如拷貝建構函式引數不是引用型別的話, 那麼將使得 ccc.CExample(aaa)變成aaa傳值給ccc.CExample(CExample ex),即CExample ex = aaa,因為 ex 沒有被初始化, 所以 CExample ex = aaa 繼續呼叫拷貝建構函式,接下來的是構造ex,也就是 ex.CExample(aaa),必然又會有aaa傳給CExample(CExample ex), 即 CExample ex = aaa;那麼又會觸發拷貝建構函式,就這下永遠的遞迴下去。
二十九、程式崩潰原因
1、讀取未賦值的變數
2、函式棧溢位
3、陣列越界訪問
4、指標的目標物件不可用
三十、C++字串輸入
1、cin>>
用法1:最基本,也是最常用的用法,輸入一個數字:
用法2:接受一個字串,遇“空格”、“TAB”、“回車”都結束
#include <iostream>
using namespace std;
main ()
{
char a[20];
cin>>a;
cout<<a<<endl;
}
輸入:jkljkljkl
輸出:jkljkljkl
輸入:jkljkl jkljkl //遇空格結束,所以不能輸入多個單詞
輸出:jkljkl
2、cin.get()
用法1: cin.get(字元變數名)可以用來接收字元
#include <iostream>
using namespace std;
main ()
{
char ch;
ch=cin.get(); //或者cin.get(ch);只能獲取一個字元
cout<<ch<<endl;
}
輸入:jljkljkl
輸出:j
用法2:cin.get(字元陣列名,接收字元數目)用來接收一行字串,可以接收空格
#include <iostream>
using namespace std;
main ()
{
char a[20];
cin.get(a,20); //有些類似getline。可以輸入多個單詞,中間空格隔開。
cout<<a<<endl;
}
輸入:jkl jkl jkl
輸出:jkl jkl jkl
輸入:abcdeabcdeabcdeabcdeabcde (輸入25個字元)
輸出:abcdeabcdeabcdeabcd (接收19個字元+1個'\0')
用法3:cin.get(無引數)沒有引數主要是用於捨棄輸入流中的不需要的字元,或者捨棄回車,彌補cin.get(字元陣列名,接收字元數目)的不足.
這個我還不知道怎麼用,知道的前輩請賜教;
3、cin.getline()
// 接受一個字串,可以接收空格並輸出
#include <iostream>
using namespace std;
main ()
{
char m[20];
cin.getline(m,5); //與上面基本相同。
cout<<m<<endl;
}
輸入:jkljkljkl
輸出:jklj
接受5個字元到m中,其中最後一個為'\0',所以只看到4個字元輸出;
如果把5改成20:
輸入:jkljkljkl
輸出:jkljkljkl
輸入:jklf fjlsjf fjsdklf
輸出:jklf fjlsjf fjsdklf
4、getline()
接受一個字串,可以接收空格並輸出,需包含“#include<string>”
#include<iostream>
#include<string>
using namespace std;
main ()
{
string str;
getline(cin,str);
cout<<str<<endl;
}
輸入:jkljkljkl //VC6中有個bug,需要輸入兩次回車。
輸出:jkljkljkl
輸入:jkl jfksldfj jklsjfl
輸出:jkl jfksldfj jklsjfl
和cin.getline()類似,但是cin.getline()屬於istream流,而getline()屬於string流,是不一樣的兩個函式
5、gets()
接受一個字串,可以接收空格並輸出,需包含“#include<string>”
#include<iostream>
#include<string>
using namespace std;
main ()
{
char m[20];
gets(m); //不能寫成m=gets();
cout<<m<<endl;
}
輸入:jkljkljkl
輸出:jkljkljkl
輸入:jkl jkl jkl
輸出:jkl jkl jkl
6、getchar()
接受一個字元,需包含“#include<string>”
#include<iostream>
using namespace std;
main ()
{
char ch;
ch=getchar(); //不能寫成getchar(ch);
cout<<ch<<endl;
}
輸入:jkljkljkl
輸出:j
//getchar()是C語言的函式,C++也可以相容,但是儘量不用或少用;
三十一、lambda表示式
一個lambda表示式表示一個可呼叫的程式碼單元,我們可以將其理解為一個未命名的行內函數。與任何函式相似,一個lambda具有一個返回型別、一個引數列表、一個函式體。但與函式不同,lambda可以定義在函式內部,一個lambda表示式具有如下形式:
[capture list捕獲列表](parameter list引數列表) {函式體},例子如下:
auto f = [](const string &a,const string &b){return a.size() < b.size()}
我們可以忽略引數列表和返回型別,但永遠包含捕獲列表和函式體,其中捕獲列表內容通常為空。
三十二、C++程式設計優化
1. 對齊原則。比如64位匯流排,每次定址讀取8B。程式設計時注意變數地址,儘量消耗匯流排最少的定址次數。堆記憶體申請時,系統嚴格按照對齊原則分配,故而使用時候也儘量不要跨定址邊界。
2. 需要的時候,可為了效率拷貝程式碼,雖然增加了程式碼體積,但這是值得的。尤其是for迴圈,若次數比較少,拆開亦無妨。
3. 位運算中,-1右移,左邊補1,故仍為-1;-1左移,右邊補0,故不再為-1。
4. 每次申請的堆記憶體,最好初始化,裡面是垃圾資料,而並非為空。
5. 專案開發中,往往一個引擎對外暴露的是一個純虛類,而其介面就是這個類的**指標變數。
6. 程式邏輯,重在語義。不能為程式碼的過分簡單而減少函式的設計。
7. *&表示對指標的引用。
8. 類的靜態方法不可呼叫其非靜態方法,亦不可呼叫非靜態成員變數。
9. 多檔案程式設計時,標頭檔案不可相互包含。
10. 標頭檔案裡儘量不要使用using namespace std;
11. static成員定義要放在cpp檔案裡面,而不是標頭檔案裡。
12. 純虛類儘量不要延續兩層以上。
13. include引用盡量都放在cpp檔案裡。
14. 子類繼承父類,不可在建構函式裡初始化父類並未在建構函式裡初始化的成員,也就是說,子類建構函式裡能初始化的成員,只有自己本身的和父類建構函式裡的。
15. 專案開發中,對於一些依賴本地環境的引數,要寫專門的配置檔案,比如伺服器地址。
16. 標頭檔案裡只宣告,不定義。在標頭檔案中,全域性變數宣告,必須加extern修飾;靜態成員變數宣告放在標頭檔案,定義放在cpp檔案,若是普通靜態變數,最好宣告和定義放在cpp,因為static作用域限於單檔案,放在cpp裡只對本檔案可見,放在標頭檔案會被所有引用該標頭檔案的cpp拷貝一份完全相同的變數。
17. linux下,進行檔案操作時,檔案路徑要採用絕對路徑(相對路徑很多時候會出bug),檔案指標要對其返回值作判斷,防止空指標。
18. debug狀態下使用assert是極好的,不過記得釋出版本前在#include <cassert>前加上#define NDEBUG,assert語句會被NDEBUG所影響。這裡多嘴一句,錯誤與異常是不同的,異常是不可避免,在釋出版本里不可或缺的,故而assert不能用於處理異常。注:在加上#define NDEBUG後,不論是除錯還是執行,assert語句都會被直接忽略,故而平時開發時把#define NDEBUG註釋掉,釋出時再啟用它。
19. 面向物件程式設計:可維護,可複用,可擴充套件,靈活性好。
20. 頻繁使用的程式碼裡,儘量少使用容器,因為其建立和回收的開銷很大。
21. 字串拼接效率:經過測試(http://bbs.csdn.net/topics/360000434和我自己專案中的實驗),memcpy效率最高,string類的+=效率特別高,足以滿足日常所需,而strcat效率很差,append也不快。
22. 基類中的虛擬函式要麼實現,要麼是純虛擬函式(絕對不允許宣告不實現,也不純虛)。
23. 在C++的類中,普通成員函式不能作為pthread_create的執行緒函式,如果要作為pthread_create中的執行緒函式,必須是static。
24. 當多個執行緒訪問同一個類靜態方法時,若該方法裡只操作棧上資料,則無妨,因為執行緒的棧是獨立的;若該方法操作了非棧上的資料(比如堆、全域性變數等),則需要互斥鎖。
25. 行內函數,定義體要放在標頭檔案,以保證每一個引用該標頭檔案的cpp裡都是完全相同的拷貝;inline關鍵字置於定義前,不必放在宣告前;若定義在類內部,可不需要inline關鍵字。
26. vector執行clear時,會自動呼叫元素(如果是類的話)的解構函式。
27. 程式設計時,對變數(尤其是全域性性質的變數或類)命名,要用解釋性的,而不能用隨意的j1,i1,n,m等名稱,容易與庫裡的變數衝突。
28. 定義巨集時,儘量多用整數,少用-1,-2之類,容易受uint和int不統一帶來的困擾。
29. 函式形參採用預設值時,要寫在聲明裡,而不寫定義裡,這樣方便呼叫其標頭檔案的cpp看得到預設值。
31. utf-8 中文編碼 三個位元組表示一個漢字
32. 專案開發時,使用條件編譯:
#define DEBUG
main()
{
#ifdef DEBUG
printf("Debugging\n");
#else
printf("Not debugging\n");
#endif
printf("Running\n");
}
釋出版本時,註釋掉第一行。這種方式要比開大量註釋來得方便。
33. 關於c字元陣列,需要注意一個初始化問題:
(1) char str[10]="";
(2) char str[10]={'\0'};
(3) char str[10]; str[0]='\0';
前兩者的意義在於str的每個元素都初始化為'\0',第三者僅僅初始化第一個元素。當陣列長度很大時,前兩者時間開銷極大。故而,有些不必要的時候,不要如此初始化。
34. 判斷一個字元是否為數字:
標頭檔案:#include <ctype.h>
定義函式:int isdigit(int c);
函式說明:檢查引數 c 是否為阿拉伯數字0 到9。
返回值:若引數c 為阿拉伯數字,則返回true,否則返回null(0)。
附加說明:此為巨集定義,非真正函式。
#include <ctype.h>
main(){
char str[] = "[email protected]#FDsP[e?";
int i;
for(i = 0; str[i] != 0; i++)
if(isdigit(str[i]))
printf("%c is an digit character\n", str[i]);
}
35. 在類裡宣告靜態常量,尤其是陣列定義的時候,如static const *str = "sdfsssf", 要在const前加上constexpr修飾,可以在編譯器就將“sdfsssf”賦值給str,達到類似巨集的效果。
36. std::string::find_last_of("/\\") 這裡的"/\\"指的是'/'或'\',在搜尋字元時用得到。
37. 專案裡c字串傳遞,多采用首地址+長度的方式,避免0x0存在導致的異常; 執行緒數要合適,大致為cpu總核數兩倍以內為佳,執行緒間切換會一定程度上消耗程式效能。
有一個陷阱是在c字串轉string型別時,c字串裡如有0,轉化時用strlen取長度就會出錯。故而,c串表示儘量維護一個len來記錄長度,而不是通過結尾0來判別。另外,strlen效率低且不安全,少用。
38. 靜態函式的實現寫在h檔案裡;儘量把h檔案裡的函式實現前都加上inline,不論其複雜度,避免被多檔案引用而引起重複定義錯誤。
39. 靜態成員變數必須在類外進行初始化。如果是複雜型別,比如容器,也要在類外定義,如std::vector<xx *> hj::sm_tr;
40. 程式設計時,一般結構體裡的堆記憶體由記憶體池管理申請或釋放,或者stl裡使用這些結構體作為元素時,使用其指標,而不是例項。因為stl裡記憶體申請或釋放,會呼叫其元素的構造和析構,這裡會有陷阱。
41. 使用set、map時,有一個陷阱,就是[]操作符。當使用這個操作符時,如果[x]裡的x並不在set或map裡,則stl會自動生成一個x元素,這樣使得stl容器內容改變,如果開發者忽略了這點,後果可能無法預期。
相關推薦
C/C++基礎知識點面試題
目錄 一、虛擬函式的資料結構,如何工作? 虛擬函式:用virtual定義的函式為虛擬函式。 虛擬函式來源:基於C++的一個特性:子類能轉換成父類,例如
【收集】C#一些基礎的面試題
C# 中 Struct 與 Class 的區別,以及兩者的適用場合class 是引用型別,str
JavaScript的基礎知識點(面試題)
1.介紹JavaScript的基本資料型別? JavaScript的基本型別是: Undefined、Null、Boolean、Number、String 2.JavaScript原型,原型鏈?有什麼特點? 每個物件都會在其內部初始化一個屬性,就是pr
位運算基礎與面試題C++
共有如下幾種位運算 運算子 功能 用法 ~ 位取反 ~expr << 左移 expr1<<ex
一道C++、MFC上機面試題
結構 鼠標 一個 不能 對話框 輸入 ext alt 試題 題目:寫一個基於MFC對話框的程序,界面輸入整型a和b,點擊計算,開啟線程計算a+b,並把結果返回給對話框。(1)不能用結構體和類(2)用到自定義消息(3)鼠標移到【計算】按鈕上變為收屍圖標。參考界面如下。 題目
【修煉C++】基礎知識點筆記-第6章 函式
1 當用實參初始化形參時會忽略掉頂層const。形參頂層const被忽略掉了,當形參有頂層const時,傳給它常量物件或者非常量物件都是可以的。 void fcn(const int i){} void fcn(int i )//錯誤:重複定義了fcn(int) 因為頂層const被忽
【修煉C++】基礎知識點筆記-第4,5章 表示式&&語句
1、條件運算子滿足右結合律,意味著運算物件(一般)按照從右向左的順序組合。 //靠右邊的條件運算(比較成績是否小於60)構成了靠左邊的條件運算的:分支。 finalgrade = (grade > 90)?"high pass":(grade < 60) ? "fail":"pa
【修煉C++】基礎知識點筆記-第3章 字串,向量和陣列
重新學習c++的東西,此為《C++ Primer》讀書筆記,主要記錄零碎的知識。另外所有的C++11新標準也會被列出。 1,位於標頭檔案的程式碼一般來說不應該使用using宣告。這是因為標頭檔案的內容會拷貝到所有引用它的檔案中。 2,拷貝 初始化,直接初始化。【???】 3,stri
【修煉C++】基礎知識點筆記-第2章
重新學習c++的東西,此為《C++ Primer》讀書筆記,主要記錄零碎的知識。另外所有的C++11新標準也會被列出。 1,C++ 語言支援分離式編譯機制,該機制允許將程式分割為若干個檔案,每個檔案可以被獨立編譯。 2,名字的作用域:全域性作用域,塊作用域。 for(int val
C++記憶體管理相關面試題
1.malloc/free和new/delete的區別 2.請設計一個類,該類只能在堆上建立物件 3.請設計一個類,該類只能在棧上建立物件 4.單例模式(這個在面試中經常出現) 5.記憶體洩漏 1.malloc/free和new/delete的區別 mall
linux C程式設計師 經典面試題(非常經典)
linux C程式設計師 經典面試題一 想成為嵌入式程式設計師應知道的0x10個基本問題: 前處理器(Preprocessor) 1 . 用預處理指令#define 宣告一個常數,用以表明1年中有多少秒(忽略閏年問題) #define SECONDS_P
linux C/C++伺服器後臺開發面試題總結
一、程式語言 1.根據熟悉的語言,談談兩種語言的區別? 主要淺談下C/C++和PHP語言的區別: 1)PHP弱型別語言,一種指令碼語言,對資料的型別不要求過多,較多的應用於Web應用開發,現在好多網際網路開發公司的主流web後臺開發語言,主要框架為mvc模型,如smarty,yaf,升級的PHP7速度較快,
C++研發工程師實習生面試題整理
//來自轉載或前人總結,用於學習,如有侵權,請聯絡刪除,謝謝 c++ baidu trainee 1.struct和union的區別 2.將int func(int A)寫成函式指標的形式 3.extern“c” 的作用以及為什麼要用到它 4.mall
c語言練習題(面試題)
5位運動員參加了10米臺跳水比賽,有人讓他們預測比賽結果 A選手說:B第一,我第三。 B選手說:我第二,E第四。 C選手說:我第一,D第二。 D選手說:C最後,我第三。 E選手說:我第四,A第一。
c語言指標相關面試題
例1 int main() { int a[5] = { 1, 2, 3, 4, 5 }; int *ptr = (int *)(&a + 1); printf("%d %d\n", *(a + 1), *(ptr - 1)
C#刷遍Leetcode面試題系列連載(1) - 入門與工具簡介
目錄 為什麼要刷LeetCode 刷LeetCode有哪些好處? LeetCode vs 傳統的 OJ LeetCode刷題時的心態建設 C#如何刷遍LeetCode 選項1:
C#刷遍Leetcode面試題系列連載(4):No.633 - 平方數之和
上篇文章中一道數學問題 - 自除數,今天我們接著分析 LeetCode 中的另一道數學題吧~ 今天要給大家分析的面試題是 LeetCode 上第 633 號問題, Leetcode 633 - 平方數之和 https://leetcode.com/problems/sum-of-square-number
C#刷遍Leetcode面試題系列連載(5):No.593 - 有效的正方形
上一篇 LeetCode 面試題中,我們分析了一道難度為 Easy 的數學題 - 自除數,提供了兩種方法。今天我們來分析一道難度為 Medium 的面試題。 系列教程索引 傳送門:https://enjoy233.cnblogs.com/articles/leetcode_csharp_index.htm
第3章 Android基礎相關面試題
sch str adapt div 再次 點擊 相關 發現 amp 1、Activity問題講解 a) Activity的生命周期 1.Activity的4種狀態 running / paused / stopped / kil
有序鏈表基礎和面試題
system 構造函數 null 打印 lis 有序鏈表 out st2 有序鏈表合並 有序鏈表的構造 class ListNode{ int val; ListNode nextNode; // 構造函數 ListNode(int