牛客網刷題第二天
1.數組初始化問題
需要注意的點:
- 數組只能夠省略第一維
- char類型的數組,最後有一個’\0’,size是需要+1的
char a[]="hello";
char a[6]="hello";
char a[5]="hello";//error
char a[][5]={"this","is"};
char a[][]={"this","is"};//error
2.二進制的運算
Q:設 char 型變量 x 中的值爲10100111,則表達式(2+x)^(~3)的值是多少?
-00000000 //對齊操作
---------
00000010 //2
+10100111 //原二進制數據
=10101001 //結果1
~00000011 //對3進行取反操作
=11111100 //結果2
10101001 //result 1
^11111100 //result 2,異或操作,相同爲0,不同爲1
=01010101 //結果3,D is right.
備註:當時將’^'當成了冪運算,實際上這些運算應該是統一的,如果系統能夠確定是二進制運算,就不會再摻進其他類型的運算。
3.變量的內存分配【·】
Q:只有在使用時才爲該類型變量分配內存的存儲類說明是 auto 和 register。
解釋:
- auto 根據數據類型分配內存,
- register 在使用到時分配CPU寄存器地址,
- static 聲明的時候就會分配內存,不初始化系統會自動初始化,
- extern 外部變量在程序被編譯時(程序運行之前)分配存儲。
相關鏈接:
1.C++ - 外部變量(extern)
2.extern和const和#ifndef和內存分配和C++的.h和.cpp
3.extern聲明變量詳解
4.C++中的static及內存分配
4.字符常量
'\007'//八進制轉義字符,表示響鈴
'\b'//ASCII碼,普通轉義字符,退格
'a'//字符a
"\09"//這是兩個字符,八進制中最多出現到7
5.靜態成員&非靜態成員
- 靜態成員存在於內存,非靜態成員需要實例化纔會分配內存
- 非靜態成員可以直接訪問類中靜態的成員
- 靜態成員不能訪問非靜態的成員
- 非靜態成員的生存期決定於該類的生存期,而靜態成員生存期則與程序生命期相同
解釋:靜態成員存在於內存,非靜態成員需要實例化纔會分配內存,所以靜態成員函數不能訪問非靜態的成員。因爲靜態成員存在於內存,所以非靜態成員函數可以直接訪問類中靜態的成員。[至少你要訪問的東西是在內存中的]
6.運算符重載
下面是C++運算符的優先級順序,【】內是不能夠重載的運算符
作用域 | 【::】 | ||||||||||
增量、對象、數組 | -> | 【.】 | ++(後) | - -(後) | [ ] | ||||||
增量、內存、取反 | ! | ~ | ++(前) | - -(前) | +(正) | -(負) | *(指針) | &(取址) | new | delete | 【sizeof】 |
類成員指針 | ->* | 【.*】 | |||||||||
算術 | * | / | % | ||||||||
算術 | + | - | |||||||||
位 | << | >> | |||||||||
關係 | < | <= | >= | > | |||||||
關係 | == | != | |||||||||
位 | & | ||||||||||
位 | ^ | ||||||||||
位 | | | ||||||||||
&& | 邏輯 | ||||||||||
邏輯 | || | ||||||||||
條件 | 【?:】 | ||||||||||
賦值 | = | += | -= | *= | /= | %= | &= | ^= | |= | ||
逗號 | , |
- .和.*是爲保護訪問成員功能而不允許被重載;
- sizeof運算對象是類型而非變量,不具備重載特徵。
我覺得對於可以重載的運算符進行重載,需要單獨寫一篇文檔。
7.二進制表示中1的個數【·】
#include <stdio.h>
int fun(int i)
{
int cnt = 0;
while(i)
{
cnt++;
i = i&(i-1); //循環下來計算的是從右往左數1的個數。
}
return cnt;
}
int main()
{
printf( "%d\n", fun(2017) );
return 0;
}
//output:7
解釋:2017的二進制是11111100001,有7個1。
8.等概率輸出數字【·】
下面的程序可以從0…n-1中隨機等概率的輸出m個不重複的數。這裏我們假設n遠大於m,直接看代碼吧:
knuth(int n, int m)
{
srand((unsigned int)time(0));
for (int i = 0; i < n; i++) {
if (rand()%(n-i)<m) { //原題目中需要填寫
cout << i << endl;
m--;//原題目中需要填寫
}
}
}
原理解釋:
由這個for循環循環n次,且在滿足條件時才輸出 i 可知,輸出m個不同值的要求已滿足,因爲每次輸出的都是 i 值,而 i 值每次都是不一樣的,m-- 保證了程序在輸出了m個值後就停止循環。
.
在i=0時,rand()%(n-i)的取值範圍爲0到n-1,共n個數,此時要輸出0只需要rand()%(n-i)小於m,故i=0被輸出的概率爲m/n;
.
在i=1時,rand()%(n-i)的取值範圍爲0到n-2,共n-1個數,若i=0沒有被輸出,則m–未被執行,此時i=1被輸出的概率爲m/(n-1),
.
若i=0已經被輸出了,則m變爲m-1,此時i=1被輸出的概率爲(m-1)/(n-1);由概率論的知識,可知此時i=1被輸出的概率爲
P=(1-m/n)(m/(n-1))+m/n((m-1)/(n-1))=m/n;
以此類推,可知每個數被輸出的概率都爲m/n.
備註:
- 這裏輸出的是 i,m只是用來判斷個數的,並不會輸出的數字逗比m小,i 是逐漸遞增的,隨機數是用來判斷的。
- 使用隨機數作爲判斷的條件,來輸出隨機數,做法比較高明。
9.進制轉換
源碼:
auto fn = [](unsigned char a){
cout << std::hex << (int)a << endl;
};
fn(-1);
//output:ff
題中形式爲C++11標準裏引入的lambda表達式,一個lambda表達式表示一個可調用的代碼單元,也可將其理解爲一個未命名的內聯函數。
其基本形式如下:
- [capture list ] ( parameter list ) -> return type { function body }
- [捕獲列表] ( 參數列表 ) -> 返回類型 { 函數體 }
- capture list (捕獲列表)是一個lambda所在函數中定義的局部變量的列表(通常爲空,寫爲 [] ),空捕獲列表即表明此lambda不使用它所在函數中的任何局部變量;
- " -> " 表明lambda使用了尾置返回類型;
- 可以忽略參數列表和返回類型,但必須永遠包含捕獲列表和函數體;
比如:
auto f = [ ] { return 42; }; // 定義了一個可調用對象f,它不接受參數,返回42。
即常見形式如:
[ ] { 函數體 }
[ ] ( int n ) { 函數體 }
回到上面的問題:
1. int型-1的存儲方式爲補碼(32位,4字節),1111 1111 1111 1111 1111 1111 1111 1111
2. 轉換爲unsigned char(8位,1字節)發生字節截斷,取最後八位爲1111 1111
3. 再轉換爲int後爲0000 0000 0000 0000 0000 0000 1111 1111
4. 輸出爲ff [hex爲16進制,四個四個的看]
這個題目中我需要知道:
- 原碼、反碼、補碼
- 計算機爲什麼使用補碼存儲數據
- C++11的lambda表達式有什麼好處
- 正數的原碼、反碼和補碼都相同。
- 負數原碼2反碼:符號位不變,數值位按位取反。
- 負數原碼2補碼:符號位不變,數值位按位取反,末位再加1。
相關鏈接:
1.c++ 補碼與整數互相轉換
2.計算機爲什麼使用補碼來存儲數據
3.計算機中帶符號的整數爲何採用二進制的補碼進行存儲?
4.C++原碼、反碼、補碼詳解
5.原碼反碼補碼的相互轉換
6.C++ 11 Lambda表達式 通俗易懂
與此類似的還有一道題目:
short int i=32769;
printf("%d\n",i);
//output:-32767
解釋:
計算機中整數以補碼的形式進行存儲,第一位是符號位
shout int 只有2字節
but 32769 is 1000 0000 0000 0001,由於最高位是1,表示複數
由上述補碼,求原碼
1000 0000 0000 0001//補碼
1111 1111 1111 1110//按位取反,臨時反碼
1111 1111 1111 1111//末位加1,原碼
=-32767
10.對象的創建
Q1:假定CSomething是一個類,執行下面這些語句之後,內存裏創建了 6 個CSomething對象。
CSomething a();//函數聲明
CSomething b(2);//構造函數調用,+1
CSomething c[3];//含有三個對象的數組,+3
CSomething &ra = b;//引用
CSomething d=b;//拷貝構造函數,+1
CSomething *pA = c;//單純是個指針
CSomething *p = new CSomething(4);//有對象生成,+1
Q2:在C++中,爲了讓某個類只能通過new來創建(即如果直接創建對象,編譯器將報錯),應該將析構函數設爲私有。
解釋: 編譯器在爲類對象分配棧空間時,會先檢查類的析構函數的訪問性,其實不光是析構函數,只要是非靜態的函數,編譯器都會進行檢查。如果類的析構函數是私有的,則編譯器不會在棧空間上爲類對象分配內存。 因此, 將析構函數設爲私有,類對象就無法建立在棧(靜態)上了,只能在堆上(動態new)分配類對象 。
11.struct、class
不同點:
struct | class | |
---|---|---|
默認繼承權限 | public | private |
默認數據訪問控制 | public | private |
模板參數 | 不能定義 | 可以用於定義模板參數 |
相同點:
可以有數據成員,方法,構造函數等。
相關鏈接:
1.C++中Struct與Class的區別與比較
12.指針數組,數組指針
這個概念有點像是馬原裏面的唯物辯證法&辯證唯物論,看誰是定語,誰是主語,相關解釋如下:
int *p[n]; []優先級高,與p先結合成數組,是個指針數組,有n個指針類型的元素
int(*p)[n]; ()優先級高,與*先結合成指針,是個數組指針,指向一個整形一維數組
同樣的,還有指針常量,常量指針:
int * const p;//指針常量,重點是個常量,這個指針是常量,指針指向的位置不能改變,也就是說不能亂指,可以亂碰指向區域的東西
const int * p;//常量指針,重點是個指針,指向常量的指針,指向的數據區內容不能變,也就是說可以亂指,但別亂碰指向區域的東西
int const * p;//同上,一個道理,const在*的前面
相關鏈接:
1.數組指針和指針數組的區別
2.指針常量和常量指針
13.結尾~~~~
總結一下,這些題目是C++比較基礎的知識,但是由於學習C++時記住了,但後來太多的依靠各種搜索引擎,以至於只是掌握的不牢固,該背的內容還是要背的,畢竟寫代碼的時候不能總靠搜索,靠API,雖說是在使用C++進行開發,但是重點在使用,而不是查!!!!
【2020年3月19日01:36:39】