c++面試題

LiuYanYGZ發表於2024-08-23

摘自:https://blog.csdn.net/Destiny_zc/article/details/118532083

文章目錄
a.0 庫
a.0.最小cpp系統構成
a.1. 不使用用自定義庫檔案
a.2.使用庫檔案
a.2.1 生成靜態庫
a.2.2 生成共享庫
a.3.使用第三庫
a.3.1 使用第三庫-共享連結庫的使用
a.3.2 使用第三庫-靜態庫的使用
1.1.c++域運算子:
1.2.i++與++i的區別
1.3.++i與i++那個效率高:
1.4.有符號變數與無符號變數的值轉換
1.5.不使用任何中間變數,交換a與b的值
1.6.C與C++有什麼不同
1.7.如何理解C++是物件導向化的,而C是程序導向化的
1.8.C++中main函式執行完後還會執行其他語句嗎
2.1 宏定義,編譯與連結
2.2 用#define 實現宏並求最大值和最小值
2.3 宏定義易錯點
2.4宏引數的連線
2.5 用宏定義得到一個陣列所含的元素個數
2.6 找錯-const
2.7 #define與const的區別
2.8 C++ const有什麼作用
2.8 static 的作用
2.9 static 全域性變數與普通的全域性變數有什麼區別
2.10 c++靜態成員
2.11 sizeof 普通變數
2.12 sizeof 計算類物件大小
2.13 sizeof 含有虛擬函式的類物件的空間大小
2.14 sizeof 計算虛繼承的空間大小
2.16 sizeof()與strlen()區別==>strlen 在C中定義,在C++中沒有;
2.17 sizeof 求聯合體的大小
2.17 \#pragma pack
2.18 行內函數
行內函數的優點:
行內函數的缺點:
2.19 行內函數與宏的區別
3.1 一般引用
3.2 指標變數的引用;
3.3 變數引用
3.4 引數引用
3.5 引用錯誤
3.6 指標和引用的區別:
3.7 傳引用比傳指標安全:
3.8 指標陣列與陣列指標
3.9 指標加1
3.10 相同內容的資料儲存地址關係
3.11 記憶體越界
3.12 指標常量與常量指標
3.13 this指標
3.14 函式指標與指標函式
3.15 typedef 用於函式指標
3.16 野指標
3.17 有了malloc/free 為什麼還需要new/delete
3.17 記憶體越界
3.18 動態記憶體傳遞
4.0 易錯
4.0.1 string 與char *的區別
4.1 不使用庫函式將數字轉換成char
4.2 字元轉數字
4.3 strcpy()的實現
4.4 尋找子串
4.5 常用c字串操作
5.位制轉換
6.1 private 資料型別
6.2 初始化的坑
6.3 解構函式與建構函式是否可以被過載
6.4 建構函式中explict與普通函式的區別
6.5 explicit 的作用
6.6 繼承類析構方式
7.類的繼承
7.1 繼承中建構函式呼叫方式
7.2 虛擬函式與純虛擬函式的區別
7.2.1 虛擬函式
7.2.2 純虛擬函式
8.1 STL
8.2 STL 刪除某一個元素後,會自動移項
8.3 list 與vector的區別
a.0 庫
a.0.最小cpp系統構成
最小cpp系統構成,只需要一個CMakeLists.txt檔案和一個main.cpp;

//CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(addlib)
add_executable(test main.cpp)

//所以最小cpp系統只需要三句cmake,如果需要新增自定義的標頭檔案,和庫檔案,則需要將其包含在add_executable();此部分見##1.

1
2
3
4
5
6
7
a.1. 不使用用自定義庫檔案
在Linux中需要使用CMake進行系統檔案的構建,如果不生成自定庫檔案時,需要將所有原始檔都新增到cmake的add_executable()中,否則編譯出錯,只能執行main.cpp,呼叫其他自定義的.h和.cpp中的函式,都會出現編譯錯誤;例如,現有一下原始檔:


//main.cpp 主程式
#include<iostream>
#include"MyHead.h"
using namespace std;
int main()
{
myprint("head test");
return 0;
}
//Myhead.hpp
#ifndef MYHEAD_H
#define MYHEAD_H
#include<string>
#include<iostream>
void myprint(std::string str);
#endif
//Myhead.cpp
#include"MyHead.h"
void myprint(std::string str)
{
std::cout<<"this my head test"<<std::endl;
std::cout<<"content is :"<<str<<std::endl;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
此時需要在將新新增的Myhead.hpp與MyHead.cpp,需要在CMakeList.txt檔案中add_executable()包含進去,告訴編譯器,有兩個個可執行檔案,分別為main.cpp,MyHead.cpp;否則,編譯無法知道Myhead.hpp中的void myprint(std::string str);是如何執行的,從而導致編譯出錯;
所以此時的CMakeList.txt如下:

cmake_minimum_required(VERSION 3.0)
project(addlib)
add_executable(test main.cpp MyHead.cpp) ## 將MyHead.cpp也新增為可執行檔案
## main.cpp 中的myprint()是從MyHead.cpp中解析;
1
2
3
4
a.2.使用庫檔案
通常我們使用的第三方庫都是非開源的,我們只要獲得第三方庫的庫檔案和標頭檔案,就可以使用;而庫檔案,就是將.cpp檔案轉換成二進位制檔案,所以此時,我們是無法知道標頭檔案中的某個函式是如何定義,即這個函式的實現方法,即所有庫都是一些函式打包的集合;為了使用這個庫,我們需要知道這個庫裡面有什麼東西,所以此時需要一本說明,而這個說明就是標頭檔案.所以為了使用第三方庫,我們至少需要兩個檔案庫檔案和標頭檔案,本節目的就是實現生成一個庫檔案和使用一個庫檔案;
在Linux中庫檔案分為靜態庫和共享庫;此處我們只討論linux下的靜態庫與共享庫;

靜態庫是以.a作為字尾名,共享庫是.so為字尾的,所有庫都是一些函式打包的集合,差別在於靜態庫每次呼叫都會生成一個副本,而共享庫則只生成一個副本.

a.2.1 生成靜態庫
我們可以將自定義的源程式生成庫檔案,此時,我們就可以不用將這個源程式新增到CMakeLists.txt中的add_executable(),為了使用這個庫檔案(即MyHead.cpp生成的二進位制程式碼),我們需要在CMakeList.txt中新新增一句命令用來將這個庫檔案和我們的main.cpp連結起來;
我們只需要改變CMakeLists.txt為:

cmake_minimum_required(VERSION 3.0)
project(addlib)
add_executable(test main.cpp)

add_library(mylib MyHead.cpp) ## 將MyHead.cpp打包生成靜態庫,即生成.a檔案
target_link_libraries(test mylib) ## 將可執行程式連結到該庫上,此時main的myprint()函式才能執行
## 此時該函式是從生成的.a檔案中解析myprint()
1
2
3
4
5
6
7
a.2.2 生成共享庫
生成共享庫的方法和靜態庫的方法類似,只需要在add_library()新增關鍵字SHARED:

cmake_minimum_required(VERSION 3.0)
project(addlib)
add_executable(test main.cpp)

add_library(mylib SHARED MyHead.cpp) ## 將MyHead.cpp打包生成共享庫,即.so
target_link_libraries(test mylib) ##將可執行程式連結到該庫上,此時main的myprint()函式才能執行
1
2
3
4
5
6
a.3.使用第三庫
由前面知道,為了使用第三庫,我們需要獲取其庫檔案的和標頭檔案,此處,我們使用自己生成的庫檔案和標頭檔案;
為了使用第三方庫,我們需要指定第三庫庫檔案與標頭檔案對應的位置,最後將原始檔與庫檔案進行連結

a.3.1 使用第三庫-共享連結庫的使用
//CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(testlib)

#設定第三庫的標頭檔案路徑與庫檔案路徑
set(INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR}/include)
set(LIBRARY_PATH_MY ${PROJECT_SOURCE_DIR}/lib)

message("incude dir:" ${INCLUDE_DIRECTORIES})
message("lib dir:" ${LIBRARY_PATH_MY})

#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#新增庫

#對於find_package找不到的外部依賴庫,可以用add_library新增
# SHARED表示新增的是動態庫==>(共享庫)
# STATIC表示新增的是靜態庫
# IMPORTED表示是引入已經存在的動態庫
add_library(mylib SHARED IMPORTED)

#指定所新增依賴庫的匯入路徑
set_target_properties(mylib PROPERTIES IMPORTED_LOCATION ${LIBRARY_PATH_MY}/libmylib.so)

# 新增標頭檔案路徑到編譯器的標頭檔案搜尋路徑下,多個路徑以空格分隔
include_directories( ${INCLUDE_DIRECTORIES} )
# 新增庫檔案路徑到編譯器的庫檔案搜尋路徑下,多個路徑以空格分隔
link_directories(${LIBRARY_PATH_MY})
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} )

#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
##連結庫
target_link_libraries(${PROJECT_NAME} mylib)
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//main.cpp
#include<iostream>
#include<MyHead.h>//#include"MyHead.h"兩者都可以
using namespace std;
int main()
{
myprint("test my link library");
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
41
42
43
44
45
a.3.2 使用第三庫-靜態庫的使用
靜態庫的新增與動態庫相同,只需要將下面兩條語句進行修改:

add_library(mylib SHARED IMPORTED)==>add_library(mylib STATIC IMPORTED) ##修改關鍵字:SHARED==>STATIC
set_target_properties(mylib PROPERTIES IMPORTED_LOCATION ${LIBRARY_PATH_MY}/libmylib.so)==>
==> set_target_properties(mylib PROPERTIES IMPORTED_LOCATION ${LIBRARY_PATH_MY}/libmylib.a) ## 指向靜態庫

1
2
3
4
cmake_minimum_required(VERSION 3.0)
project(testlib)

#設定第三庫的標頭檔案路徑與庫檔案路徑
set(INCLUDE_DIRECTORIES ${PROJECT_SOURCE_DIR}/include)
set(LIBRARY_PATH_MY ${PROJECT_SOURCE_DIR}/lib)

message("incude dir:" ${INCLUDE_DIRECTORIES})
message("lib dir:" ${LIBRARY_PATH_MY})

#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#新增庫

#對於find_package找不到的外部依賴庫,可以用add_library新增
# SHARED表示新增的是動態庫==>(共享庫)
# STATIC表示新增的是靜態庫
# IMPORTED表示是引入已經存在的靜態庫
add_library(mylib STATIC IMPORTED)

#指定所新增依賴庫的匯入路徑
set_target_properties(mylib PROPERTIES IMPORTED_LOCATION ${LIBRARY_PATH_MY}/libmylib.a)

# 新增標頭檔案路徑到編譯器的標頭檔案搜尋路徑下,多個路徑以空格分隔
include_directories( ${INCLUDE_DIRECTORIES} )
# 新增庫檔案路徑到編譯器的庫檔案搜尋路徑下,多個路徑以空格分隔
link_directories(${LIBRARY_PATH_MY})
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} )


#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
##連結庫
target_link_libraries(${PROJECT_NAME} mylib)
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

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
1.1.c++域運算子:
#include<iostream>
using namespace std;
int value=0;
void printvalue()
{
cout<<value<<endl;
}
int main()
{
int value=10;
cout<<value<<endl;

/*在c++中可以透過域運算子"::",來直接操作全域性變數;但在c中,不支援這個操作,因此會報錯*/
::value=100;//修改全域性變數的值;

printvalue();
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.2.i++與++i的區別
#include<iostream>
using namespace std;
int main()
{
int i=8;
cout<<++i<<endl;//i先自增,再列印i的值;->9
cout<<--i<<endl;//i先自減,再列印i的值;->8
cout<<i++<<endl;//先列印i的值,再自增;->8
cout<<i--<<endl;
cout<<-i++<<endl;//這裡"-"表示負號運算,因此先列印-i的值,再i自增1;->-8
cout<<-i--<<endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
1.3.++i與i++那個效率高:
對於內建資料型別的情況下,效率沒有區別,因為兩者產生彙編程式碼基本是一致的;
i++;
//mov eax, DWORD PTR _i$[ebp]
//add eax,1
//mov DWORD PTR _i$[ebp],eax
++i;
//mov ecx, DWORD PTR _i$[ebp]
//add ecx,1
//mov DWORD PTR _i$[ebp],ecx
1
2
3
4
5
6
7
8
自定義資料情況(類),++i的效率較高;
對於自定義資料型別,因為字首式(++i)可以返回物件的引用,而字尾式(i++)必須返回物件的值,所以導致在大物件的時候,產生較大的複製的開銷,引起效率降低;
1.4.有符號變數與無符號變數的值轉換
#include<iostream>
#include <limits.h>
using namespace std;
int main()
{

unsigned int i=0; //注意這裡i是一個無符號值;
//最小值是0;
cout<<"unsigned int max:"<<UINT_MAX<<endl;//MAX:4294967295

cout<<i<<endl;//->0

i=-1;//比0小,超出unsigned int範圍,返回一個較大值->UINT_MAX-|-1+1|=UINT_MAX,相當於,UINT_MAX的倒數第一個值;->4294967295
cout<<i<<endl;
i=-3;//同理,UINT_MAX-|-3+1|=UINT_MAX-2=4294967293;
cout<<i<<endl;

//------------------------------------------------------------------------------------------------------------
char c;
c=100;//給char 型別賦值一個int型別,相當於將ASCII碼為100對應的字元賦值給c;
cout<<c<<endl;//->d的ASCII=100
printf("%d\n",c);//->100,其中%d相當於強制轉換.等價與->cout<<(int)c<<endl;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1.5.不使用任何中間變數,交換a與b的值
#include<iostream>

using namespace std;
int swap1(int &x,int &y)
{//採取中間變數法
int tmp=x;
x=y;
y=tmp;
}
int swap2(int &x,int &y)
{//採用加減法,x+y,x-y可能導致,資料溢位
x=x+y;
y=x-y;
x=x-y;
}
int swap3(int &x,int &y)
{//採用異或演算法;推薦使用
//異或:兩元素相同為0,不同為1;
x^=y;
y^=x;
x^=y;
}
int main()
{
int x=2,y=3;
swap1(x,y);
cout<<x<<"\t"<<y<<endl;//->3 2

swap2(x,y);
cout<<x<<"\t"<<y<<endl;//->2 3;前面已經交換了一次

swap3(x,y);
cout<<x<<"\t"<<y<<endl;//->3 2
//x=2==>010,y=3==>011
//x^y=001==>x;
//y^x=010==>y;y=2;
//x^y=011==>x;x=3;

}

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
1.6.C與C++有什麼不同
C是一個結構化語言,它的重點在於演算法和資料結構,對於語言本身而言,C是C++的子集,C++是對C的擴充,引入了過載,行內函數,異常處理等.

1.7.如何理解C++是物件導向化的,而C是程序導向化的
C是程序導向化的,但是C++不是完全物件導向化的.在C++中也可以寫出C一樣的過程化的程式,所以只能說C++擁有物件導向的特性;

1.8.C++中main函式執行完後還會執行其他語句嗎
如果定義了類的化,會呼叫解構函式,進行資源釋放;
執行atexit()註冊的函式.表示函式正常結束時,要被呼叫的函式;
atexit()函式:函式的引數是一個函式指標,函式指標指向一個沒有引數也沒有返回值的函式;
一個程式中最多可以用atexit()函式註冊32個處理函式,且這些處理函式的呼叫順序與其註冊順序相反;
#include<iostream>
using namespace std;
//需要註冊的函式沒有返回值,也沒有引數
void exit1()
{
std::cout<<"exit1..."<<endl;
}
void exit2()
{
std::cout<<"exit2..."<<endl;
}
int main()
{
std::atexit(exit1);//先註冊的後執行
std::atexit(exit2);

std::cout<<"主函式執行完畢"<<endl;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2.1 宏定義,編譯與連結
編譯:編譯的時候檢查語言錯誤,包括關鍵字對應的語義邏輯,括號匹配,變數和函式存在定義或宣告等
連結:連結的時候,就要真正把需要呼叫的各種變數和函式的二進位制程式碼匹配起來,比如你使用了某個變數,你使用之前宣告為外部定義,而實際上你沒有給出過實際定義,這是就會報錯了。這是所有可執行程式碼檢測的過程。之前編譯是每個檔案單獨變數,生成obj檔案。
不帶引數宏定義: #define 宏名 字串:#define PI 3.14 ==>編譯時,將所有PI進行替換=3.14,因為替換是在編譯之前進行,所以不會對宏名(PI)替換的物件(3.14)進行語法檢查,此時的3.14不應該被看作為實數型,而應被看作為一個普通的字串常量
帶引數宏定義 : define 宏名(參數列) 字串
#define S(a,b) a*b //宏定義中,雖沒有定義返回值,但是,是可以透過宏返回運算結果的
...
double result;
result=S(3,2)//預編譯結束後,S(3*2)被3*2代替;
//此時,3為a的實際引數,2為b的實際引數,則宏名S(3,2)經過預處理後,用3*2替換後再進行編譯.即S(a*b)等同行於a*b,而S(3,2)等同於3*2;
1
2
3
4
5
採用宏定義編譯的時候,編譯器會"機械"的將所定義的字元進行替換,這種替換可能產生錯誤;

2.2 用#define 實現宏並求最大值和最小值
#include <iostream>
using namespace std;
#define MAX(x, y) (((x) > (y)) ? (x) : (y)) //可以將結果直接返回
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
int main()
{
int a = 2, b = 3, result;
result = MAX(a, b);
std::cout << result << endl;

result = MIN(a, b);
std::cout << result << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
在宏的定義過程中,由於直接替換可能導致,意外的結果,所以需要用括號,將引數括起來.提高優先順序;

2.3 宏定義易錯點
#include <iostream>
using namespace std;
#define SQR(x) (x * x)
int main()
{
int a, b = 3;
a = SQR(b + 2);//->輸出的是11,而不是25;
std::cout << a << endl;
}
1
2
3
4
5
6
7
8
9
這是由於,宏定義展開是在預處理時期,也就是在編譯之前,此時b並沒有被賦值,這是b只是一個符號.因此展開為->a=(b+2*b+2);所以,進行計算後,得出11;
改正:
可以將#define SQR(x) (x * x)==>#define SQR(x) ((x) * (x))

2.4宏引數的連線
首先來介紹一下這兩種功能:
#的用法是負責將其後面的東西轉化為字串,比如:

#define TO_STRING(str) #str
int main(){
cout << TO_STRING(this is a string) << endl;
return 0;
}
1
2
3
4
5
##是連線符,將前後兩個東西連線成一個詞。比如:

#define IntVar(i) int_##i

int main(){
int int_1 = 1, int_2 = 2;
cout << IntVar(1) << ", " << IntVar(2) << endl;
return 0;
}
1
2
3
4
5
6
7
#include <iostream>
using namespace std;
/*使用#把宏引數變成一個字串,用##把兩個宏引數貼合在一起*/
#define STR(s) #s
#define CONS(a,b) (int)(a##e##b)
int main()
{
cout<<STR(vck)<<endl;
cout<<CONS(2,3)<<endl;
}
1
2
3
4
5
6
7
8
9
10
2.5 用宏定義得到一個陣列所含的元素個數
#include <iostream>
using namespace std;
#define ARR_SIZE(a) (sizeof((a))/sizeof((a[0])))//在編譯時,只會將ARR_SIZE(a)替換,此時陣列a並不存在,所以需要在使用時,指定陣列;
int main()
{
int arry[100];
int size=ARR_SIZE(arry);
cout<<size<<endl;
}
1
2
3
4
5
6
7
8
9
2.6 找錯-const
#include <iostream>
using namespace std;

int main()
{
const int x=1;
int b=10;
int c=20;

/*常量指標:,指標指向的內容是不可改變的,指標看起來好像指向了一個常量。*/
/*指標常量:在指標常量中,指標自身的值是一個常量,不可改變,始終指向同一個地址。在定義的同時必須初始化。*/
const int * a1=&b; //常指標,指向常數
int *const a2=&b; //指標常量,不能改變指標
const int *const a3=&b; //指向常量的常指標;

x=2; //x是const型別,所以不能直接修改

a1=&c; //正確

*a1=1;//錯誤,a1指向是一個常量,不可以被修改

a2=&c;//錯誤,指標常量只能在定義時初始化,不可以被賦值;
*a2=1;//正確
a3=&c;//錯誤,指標常量只能在定義時初始化,不可以被賦值;
*a3=1;//錯誤;a3指向是一個常量,不可以被修改,同時a3也不可以被修改;
}

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
2.7 #define與const的區別
#define 只是用來作文字替換;
#define PI 3.1415926
1
當程式進行編譯的時候,編譯器會首先將"#define PI 3.1415926"以後所有的程式碼中"PI"用3.141592進行替換,然後進行編譯.它的生命期止於編譯期,它存在程式的程式碼段,實際程式中他只是一個常數,一個命令中的引數,並沒有實際存在;
const
const 存在與程式的資料段,並在堆疊上分配空間.const 常量是一個run-time型別的,它在程式中確確實實存在,並可以被呼叫/傳遞.編譯器可以對const常量進行安全檢查;
2.8 C++ const有什麼作用
const用於定義常量:const用於定義常量,編譯器可以對其進行資料靜態型別安全檢查;
const修飾函式形式的引數;當輸入引數為使用者自定義型別和抽象資料型別時,應該將值傳遞改為const &傳遞,可以提高效率;
void fun(A a);
void fun(A const & a);
1
2
第一個函式效率低,函式體產生的A型別的臨時物件用於複製引數a,臨時物件的構造,複製,析構過程都將會消耗時間.
第二個函式,用引用傳遞,不需要產生臨時物件,節省了臨時物件的構造,複製,析構過程消耗的時間.但是光用引用有可能改變a,所以加const.
const 修飾函式的返回值;如給"指標傳遞"的函式返回值加const,則返回值不能被直接修改,且返回值只能被賦值給加const修飾的同型別指標;
const修飾類的成員函式:任何不會修改資料成員的函式都應用const修飾,這樣,當不小心修改了資料成員呼叫了非const成員函式,編譯器就會報錯.
2.8 static 的作用
在模組內(在函式體外),一個被宣告的靜態變數,可以被模組內所有的函式訪問,但不能被模組外其他函式訪問,它是一個本地的全域性變數;作用域從定義位置起結束;
在模組內,一個被宣告為靜態的函式只可被這一模組內的其他函式呼叫,這個函式被限制在宣告它的模組的本地範圍內使用.
2.9 static 全域性變數與普通的全域性變數有什麼區別
儲存方式:兩者是相同的,均是靜態儲存,儲存在靜態儲存區.
作用域:不同,非靜態全域性變數的作用域是整個源程式,當一個程式有多個原始檔組成時,非靜態全域性變數在各個原始檔之間都是有效的.而靜態全域性變數的則限制了其作用域,即只在定義該變數的原始檔內有效,在同一個源程式的其它原始檔中不能使用它;
tips:static 函式與普通函式的區別,static 函式在記憶體中只有一份,普通函式在每次呼叫時,維持一份賦值品;
2.10 c++靜態成員
類的靜態成員,是類的公共成員,不同物件共享這一個成員;
靜態成員,只能透過靜態成員函式進行訪問,而靜態成員函式不僅可以透過類物件進行呼叫,也可以透過類::靜態成員函式進行呼叫;

#include <iostream>
using namespace std;
class widget
{
private:
static int count;
public:
widget(/* args */);
static int num() //!靜態成員,必須透過靜態成員函式進行操作;
{
return count;
}
~widget();
};

widget::widget(/* args */)
{
count++;
}

widget::~widget()
{
--count;
}
int widget::count=0;//!靜態成員是公共成員變數,必須在類外進行初始化;
int main()
{
widget x,y;
cout<<"widget count:"<<widget::num()<<endl; //==>2
widget z;
cout<<"widget count:"<<widget::num()<<endl; //==>3
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
結果分析:
輸出的結果是2,3.由於靜態成員資料是公共成員資料,所以共享成員資料,在建構函式中 count++;也就是說,每構造一個類成員,都會導致count++,所以,count相當於記錄著類成員的個數;

2.11 sizeof 普通變數
#include <iostream>
using namespace std;
void fun(int arr_[100])
{
cout<<sizeof(arr_)<<endl;//==> 8;//因為在傳遞時候,傳遞是陣列arr的地址,所以佔用8個位元組(64位);
}
int main()
{
int arr[100];
char str[] = "Hello";
int *p = arr;

cout << sizeof(arr) << endl;//==>400;此時計算的是一個陣列的總共大小;
cout << sizeof(str) << endl;//==>6 :5個字元+'/0'
cout << sizeof(p) << endl; //?==>8 (64位)
/*
無論是什麼型別的指標變數,在32位系統下,一個指標變數所佔用的空間是4個位元組,
在64位下,一個指標變數所佔用的空間是8個位元組。
64位作業系統下,定址範圍的最大長度為64bit,需要用16個十六進位制數。
表示16個十六進位制數,需要4乘16bit,即8Byte
*/
fun(arr);//注意傳遞是陣列的首地址,而不整個陣列;
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
2.12 sizeof 計算類物件大小
#include <iostream>
using namespace std;
class A
{
public:
int i;
};
class B
{
public:
char ch;
};
class C
{
public:
int i; //最大基本資料型別:4位元組
short j;
};
class D
{
public:
int i; //最大基本資料型別:4位元組
short j;
char ch;
};
class E
{
public:
int i;
int ii;
short j;
char ch;
char chr;
};
class F
{
public:
int i;
int ii;
int iii;
short j;
char ch;
char chr;
};

int main()
{

cout << "sizeof(int)=" << sizeof(int) << endl; //==>4
cout << "sizeof(short)=" << sizeof(short) << endl; //==>2
cout << "sizeof(char)=" << sizeof(char) << endl; //==>1

cout << "sizeof(A)=" << sizeof(A) << endl; //==>4
cout << "sizeof(B)=" << sizeof(B) << endl; //==>1
cout << "sizeof(C)=" << sizeof(C) << endl; //==>8
cout << "sizeof(D)=" << sizeof(D) << endl; //==>8
cout << "sizeof(E)=" << sizeof(E) << endl; //==>12
cout << "sizeof(F)=" << sizeof(F) << endl; //==>16
}

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
易錯點:
對於 A B類的大小是比較容易理解的,但是為什麼C D E F 輸出不是 6 7 12 15?而是 8 8 12 16.這是由於位元組對齊引起的.
位元組對齊是為了提高讀取效率,假如某硬體平臺是從偶數字節進行讀取,那麼如果一個int型別從偶數字節開始儲存,那麼一個讀週期就可以讀取,若該int型別從奇數字節開始儲存,那麼可能需要2個讀週期.
所以為了滿足位元組對齊:
對於C:基本最寬的基本型別為int,大小為4個位元組,需要在補三個位元組,湊成4的倍數.
所以,位元組對齊最終使得,該類佔用的位元組數為,最大基本型別的 整數倍

2.13 sizeof 含有虛擬函式的類物件的空間大小
#include <iostream>
using namespace std;
class Base
{
private:
int a;
public:
Base(int x);
void print()
{
cout << "base" << endl;
}
};

Base::Base(int x) : a(x)
{
}

class Derived : public Base
{
private:
int b;

public:
Derived(int x);
void print()
{
cout << "derived" << endl;
}
};

Derived::Derived(int x) : Base(x - 1), b(x)
{
}

class A
{
private:
int a;
public:
A(int x):a(x){}
virtual void print()
{
cout << "A" << endl;
}
};

class B : public A
{
private:
int b;

public:
B(int x):A(x-1),b(x){}
virtual void print()
{
cout << "B" << endl;
}
};

int main()
{
Base obj(1);
cout << "size of Base obj is:" << sizeof(obj) << endl; //==> 4 對於Base類,它佔用的記憶體大小sizeof(int)=4,print()函式不佔記憶體

Derived obj2(2);
cout << "size of Derived obj is:" << sizeof(obj2) << endl; //==>8 比Base 多一個 int ,print()函式不佔記憶體;

A a(1);
cout << "size of A obj is:" << sizeof(a) << endl; //==>16?
//!對於含有虛擬函式的類,不僅需要給資料成員分配記憶體,還需要包含一個隱含的虛表指標成員,
//!所以,記憶體大小應該為:sizeof(int)+指標大小; 應該等於 4+8=12,為什麼這裡是16?

B b(2);
cout << "size of B obj is:" << sizeof(b) << endl; //==>16 比A多一個int
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
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
所以可見,普通函式不佔用記憶體,只要有虛擬函式,就會佔用一個指標大小的記憶體,原因是是系統多用一個指標維護這個類的虛擬函式表,並且無論多少含有多少個虛擬函式,都只會產生一個指標.

2.14 sizeof 計算虛繼承的空間大小
#include <iostream>
using namespace std;
class A
{
};
class B
{
};
class C : public A, public B
{
};
class D : virtual public A
{
};
class E : virtual public A, virtual public B
{
};
class F
{
public:
int a;
static int b;
};
int F::b = 10;
int main()
{
cout << "sizeof(A)=" << sizeof(A) << endl; //!==>1,因為沒有成員資料,所以編譯器會安插一個char給空類,用來標記它的每一個物件;
cout << "sizeof(B)=" << sizeof(B) << endl; //==>1,因為沒有成員資料,所以編譯器會安插一個char給空類,用來標記它的每一個物件;
cout << "sizeof(C)=" << sizeof(C) << endl; //==>1,多繼承A B,所以編譯器會安插一個char給空類,用來標記它的每一個物件;
cout << "sizeof(D)=" << sizeof(D) << endl; //==>8(4),虛繼承A,編譯器為該類安插一個指向父類的指標;
cout << "sizeof(E)=" << sizeof(E) << endl; //?==>8,虛繼承A,B,此處不應該為16?兩個指標,指向兩個父類;
cout << "sizeof(F)=" << sizeof(F) << endl; //!==>4,存在一個靜態成員,這個靜態成員的空間,不存在類的例項中,而像全域性變數一樣,儲存在靜態儲存區;
}


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
2.16 sizeof()與strlen()區別==>strlen 在C中定義,在C++中沒有;
sizeof 是運算子,strlen()是函式;
sizeof 返回值型別不一樣,sizeof返回值是size_t,strlen()返回值是unsigned int;
sizeof 可以使用多個型別作為引數,strlen()只能使用char *作為引數;
#include <iostream>
extern "C"
{
#include <string.h>
}

using namespace std;
int main()
{
char *str = "hello";
cout << sizeof(str) << endl; //==> 8,指標儲存空間大小
cout << strlen(str) << endl; //==>5,字串長度,不包含'/0'
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2.17 sizeof 求聯合體的大小
#include <iostream>
using namespace std;
union u
{
double a;
int b;
};
union u2
{
char a[13];
int b;
};
union u3
{
char a[13];
char b;
};

int main()
{
cout << "sizeof(double)=" << sizeof(double) << endl; //==>8
cout << "sizeof(u)=" << sizeof(u) << endl; //==>8,union,分配記憶體的大小取決於,它的成員中佔用空間最大的一個,
//同時還要滿足**位元組對齊**;

cout << "sizeof(u2)=" << sizeof(u2) << endl; //==>16,本應該分配13個位元組,但是有int 型別,所以還應該滿足,位元組對齊;
cout << "sizeof(u3)=" << sizeof(u3) << endl; //==>13
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
2.17 #pragma pack
#pragma pack 的主要作用就是改變編譯器的記憶體對齊方式,這個指令在網路報文的處理中有著重要的作用,
#pragma pack(n)是他最基本的用法,其作用是改變編譯器的對齊方式, 不使用這條指令的情況下,編譯器
預設採取#pragma pack(8)也就是8位元組的預設對齊方式,n值可以取(1, 2, 4, 8, 16) 中任意一值。

#include <iostream>
using namespace std;
/*
#pragma pack 的主要作用就是改變編譯器的記憶體對齊方式,這個指令在網路報文的處理中有著重要的作用,
#pragma pack(n)是他最基本的用法,其作用是改變編譯器的對齊方式, 不使用這條指令的情況下,編譯器
預設採取#pragma pack(8)也就是8位元組的預設對齊方式,n值可以取(1, 2, 4, 8, 16) 中任意一值。
*/
#pragma pack(16) //設定一個位元組對齊;
struct test
{
char c;
short s1;
short s2;
int i;
};

int main()
{
test ts;
cout<<sizeof(ts)<<endl; //==>9
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2.18 行內函數
行內函數是C++中的一種特殊函式,它可以像普通函式一樣被呼叫,但是在呼叫時並不透過函式呼叫的機制而是透過將函式體直接插入呼叫處來實現的,這樣可以大大減少
由函式呼叫帶來的開銷,從而提高程式的執行效率。一般來說inline用於定義類的成員函式。
inline的使用比較簡單,只需要在宣告或者定義函式時在頭部加上inline關鍵字即可,格式如下:

inline 返回值型別 函式名(函式引數){}
1
一般來說,inline適用的函式有兩種,一種是在類內定義的成員函式,另一種是在類內宣告,類外定義的成員函式,對於這兩種情況inline的使用有一些不同:

(1)類內定義成員函式
這種情況下,我們可以不用在函式頭部加inline關鍵字,因為編譯器會自動將類內定義的函式宣告為行內函數,程式碼如下:

class temp{
public:
int amount;

//建構函式
temp(int amount){
this->amount = amount;
}

//普通成員函式,在類內定義時前面可以不加inline
void print_amount(){
cout << this-> amount;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
從上面的程式碼可以看出,在類內定義函式時,可以不加inline關鍵字,編譯器會自動將類內定義的函式(建構函式、解構函式、普通成員函式等)設定為內聯,具有行內函數呼叫的性質。
(2) 類內宣告函式,在類外定義函式
根據C++編譯器的規則,這種情況下如果想將該函式設定為行內函數,則可以在類內宣告時不加inline關鍵字,而在類外定義函式時加上inline關鍵字,程式碼如下所示:

class temp{
public:
int amount;

//建構函式
temp(int amount){
this->amount = amount;
}

//普通成員函式,在類內宣告時前面可以不加inline
void print_amount()
}

//在類外定義函式體,必須在前面加上inline關鍵字
inline void temp:: print_amount(){
cout << amount << endl;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
行內函數的優點:
1.inline 定義的類的行內函數,函式的程式碼被放入符號表中,在使用時直接進行替換,(像宏一樣展開),沒有了呼叫的開銷,效率也很高。

2.很明顯,類的行內函數也是一個真正的函式,編譯器在呼叫一個行內函數時,會首先檢查它的引數的型別,保證呼叫正確。然後進行一系列的相關檢查,就像對待任何一個真正的函式一樣。這樣就消除了它的隱患和侷限性。(宏替換不會檢查引數型別,安全隱患較大)

3.inline函式可以作為一個類的成員函式,與類的普通成員函式作用相同,可以訪問一個類的私有成員和保護成員。行內函數可以用於替代一般的宏定義,最重要的應用在於類的存取函式的定義上面。

行內函數的缺點:
內聯是以程式碼膨脹(複製)為代價,僅僅省去了函式呼叫的開銷,從而提高函式的執行效率.如果執行函式體內程式碼的時間相比函式呼叫的開銷較大時,效率就不如採用呼叫方式高;
行內函數具有一定的侷限性,行內函數的函式體一般來說不能太大,如果行內函數的函式體過大,一般的編譯器會放棄內聯方式,而採用普通的方式呼叫函式。(換句話說就是,你使用行內函數,只不過是向編譯器提出一個申請,編譯器可以拒絕你的申請)這樣,行內函數就和普通函式執行效率一樣了。
2.19 行內函數與宏的區別
行內函數在編譯時展開,宏在預編譯時展開;
在編譯的時候,內斂函式可以直接被鑲嵌到目的碼中,而宏只是一個簡單文字替換;
行內函數在編譯時,對語法進行檢查,宏定義在使用時只是簡單的文字替換,並沒有做嚴格的引數檢查,也就不能享受C++編譯器嚴格型別檢查的好處,另外它的返回值也不能被強制轉換為可轉換的合適的型別,這樣,它的使用就存在著一系列的隱患和侷限性。
3.1 一般引用
進行引用時,新定義的引用變數不開闢儲存空間;
一般引用必須在宣告的同時進行繫結,後面不能重新繫結變數,即,引用只能在宣告的時候被賦值,以後都不能再把引用名作為其他變數名的別名;
;
#include <iostream>
using namespace std;

int main()
{
int a = 50, b = 100;
int &c = a; //!不需要給c重新開闢新的儲存空間,a與c佔用一個儲存單元;
cout << a << " " << b << endl; //==>50 100
c = 25;
cout << a << " " << b << endl; //==>25 100

int qual=(&a==&c)?1:0;

cout<<qual<<endl; //==> 1,由此可見,將c宣告為a的別名,不需要給c重新開闢新的儲存空間,a與c佔用一個儲存單元;
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
3.2 指標變數的引用;
指標變數引用必須在宣告的同時進行繫結,後面可以重新繫結變數;

#include <iostream>
using namespace std;

int main()
{
int a = 1;
int b = 10;

int *p = &a;
int *&pa = p; //指標變數的引用,需要在定義時進行繫結;

(*pa)++; //pa是p的別名;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "*p=" << *p << endl;

pa = &b; //pa繫結到b;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "*p=" << *p << endl;

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
3.3 變數引用
#include <iostream>
using namespace std;

int main()
{
int a = 1;
int b = 2;

int &c; //!錯誤,引用型別變數在宣告的同時**必須**初始化;
int &d = a;

&d = b;//!錯誤,將d當做b的別名,引用只能在宣告的時候被賦值,以後都不能再把引用名作為其他變數名的別名;

int *p; //! 野指標;容易出錯;
*p = 5;
cout << *p << endl;
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3.4 引數引用
#include <iostream>
using namespace std;
const float pi=3.1415926;
float f;
float f1(float r) //返回值,透過該函式返回引用,只能返回一個臨時變數,所以會出現編譯錯誤;
{
f=r*r*pi;
cout<<f<<endl;
return f;
}
float & f2(float r) //返回引用
{
f=r*r*pi;
return f;
}
int main()
{
//--------------------------------------------------------
float f1(float=5);//!宣告f1()的預設引數呼叫,預設值為5;
float& f2(float=6);//!宣告f2()的預設引數呼叫,預設值為6;
//--------------------------------------------------------

float a=f1();
float &b=f1(); //錯誤;將變數b賦為f1()的返回值.因為在f1()函式里,全域性變數f的值78.5賦值給一個**臨時變數**,
//這個臨時變數是由編譯器**隱式**地建立,然後建立這個臨時變數的引用,此時對臨時變數進行引用,就會發生編譯錯誤;

float c=f2();

float &d=f2(); //此種方式返回的是全域性變數的引用,而全域性變數的宣告週期大於d,所以引用是有效的;
cout<<f<<endl;
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
3.5 引用錯誤
const 型別可以引用非const 型別:
int a=10;
const int &b=a;//此時b是const 常量,所以,不能改變b的值,例如:b++;是違法的;
1
2
非const 型別,不能引用const:
const int a=10;
int &b=a;//b是一個變數,可以改變其值,但是a是一個常量;
1
2
#include <iostream>
using namespace std;
class Test
{
public:
void func(const int &arg)
{
//arg=10;//! 錯誤:arg是一個常量的引用,所以arg的值在函式內不能被修改;
cout << "arg=" << arg << endl;
value = 20;
}

private:
int value;
};
int main()
{
int a = 7;
const int b = 10;
//int &c=b; //!錯誤,const 型別可以引用變數,但是,變數不可以引用const型別;==>const int &c=b;
const int &d = a;

a++;
//d++; //! d是常量引用,不能對d賦值,但是,可以改變a的值,所以const 變數是不能直接改變其值,但是可以間接改變其引用的值;

Test test;

test.func(a); //!

cout << "a=" << a << endl;
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
3.6 指標和引用的區別:
初始化要求不同:
引用: 引用在建立的同時必須初始化,即引用到一個有效的物件;
指標: 指標在建立的時候,可以不初始化,可以在後面重新賦值;
可修改性不同:
引用:引用一旦初始化為指向一個物件,它就不能被改變為另一個物件的引用;
指標:指標在任何時候都可以改變指向一個物件;
不存在NULL引用:
引用: 引用不能使用指向空值的引用,它必須總是指向某一個物件;
指標: 可以指向NULL;
3.7 傳引用比傳指標安全:
引用:
由於引用不存在空引用,並一旦初始化為指向一個物件,它就不能被改變為另一個物件的引用,因此比較安全;
指標:
指標可以隨時改變指向別的物件,並且可以不被初始化或為NULL,甚至可以出現野指標,所以不安全;

3.8 指標陣列與陣列指標
指標陣列:指標陣列可以說成是”指標的陣列”,首先這個變數是一個陣列,其次,”指標”修飾這個陣列,意思是說這個陣列的所有元素都是指標型別,在32位系統中,指標佔四個位元組。
陣列指標:陣列指標可以說成是”陣列的指標”,首先這個變數是一個指標,其次,”陣列”修飾這個指標,意思是說這個指標存放著一個陣列的首地址,或者說這個指標指向一個陣列的首地址。

int *p1[5];
int (*p2)[5];
1
2
首先,對於語句“intp1[5]”,因為“[]”的優先順序要比“”要高,所以 p1 先與“[]”結合,構成一個陣列的定義,陣列名為 p1,而“int*”修飾的是陣列的內容,即陣列的每個元素。也就是說,該陣列包含 5 個指向 int 型別資料的指標,如圖 1 所示,因此,它是一個指標陣列。

其次,對於語句“int(p2)[5]”,“()”的優先順序比“[]”高,“”號和 p2 構成一個指標的定義,指標變數名為 p2,而 int 修飾的是陣列的內容,即陣列的每個元素。也就是說,p2 是一個指標,它指向一個包含 5 個 int 型別資料的陣列,如圖 2 所示。很顯然,它是一個陣列指標,陣列在這裡並沒有名字,是個匿名陣列。

由此可見,對指標陣列來說,首先它是一個陣列,陣列的元素都是指標,也就是說該陣列儲存的是指標,陣列佔多少個位元組由陣列本身決定;而對陣列指標來說,首先它是一個指標,它指向一個陣列,也就是說它是指向陣列的指標,在 32 位系統下永遠佔 4 位元組,至於它指向的陣列佔多少位元組,這個不能夠確定,要看具體情況。

此段摘錄:陣列指標和指標陣列的區別

#include <iostream>
using namespace std;

int main()
{
//指標陣列:陣列儲存指標;
int *p1, *p2, *p3;
int *a[4] = {p1, p2, p3};

//arr就是我定義的一個指標陣列,它有四個元素,每個元素是一個char *型別的指標,這些指標存放著其對應字串的首地址。
char *arr[4] = {"hello", "world", "shannxi", "xian"};
for (int i = 0; i < 4; i++)
{
cout << arr[i] << endl;
}

//陣列指標:一個指標指向一個陣列;
int b[3] = {10, 20, 30};
int(*p)[3] = &b; //!一個指標,指向一個含有3個整型的陣列;==>必須型別 數量 都對應相等;
/*下面是錯誤的*/
//int (*p2)[5] = b;

for (int i = 0; i < 3; i++)
{
cout<<p<<endl;
cout<<*p<<endl;
cout << "陣列地址arr:"<<&b[i]<< endl;
cout << "陣列地址*p:"<<(*p+i) << endl;
}
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
指標陣列與陣列指標:TODO==>有點繞;

#include <iostream>
using namespace std;
int main()
{
//指標陣列:陣列裡面存放的指標;
char *str[] = {"welcome", "to", "Fortemedia", "Nanjing"};
char **p = str + 1;

str[0] = (*p++) + 2;
str[1] = *(p + 1);
str[2] = p[1] + 3;
str[3] = p[0] + (str[2] - str[1]);

cout << str[0] << endl;
cout << str[1] << endl;
cout << str[2] << endl;
cout << str[3] << endl;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3.9 指標加1
指標 + 1 並不是指標代表的地址值 + 1.一個型別為T的指標的移動,是以sizeof(T)為移動單位。
指標變數加1,即向後移動1 個位置表示指標變數指向下一個資料元素的首地址。而不是在原地址基礎上加1。至於真實的地址加了多少,要看原來指標指向的資料型別是什麼。

char a = 'a';
char *p = &a;
cout<<(void*)p<<" "<<(void*)(p+1)<<endl;
//輸出:0012FF33 0012FF34
1
2
3
4
p指向的是一個字元,p+1就是移動一個字元大小,一個字元就是一個位元組,所以p +1 代表的地址就比 p 代表的地址大1。

int i = 1;
int *p = &i;
cout<<(void*)p<<" "<<(void*)(p+1)<<endl;
//輸出:0012FF30 0012FF34
1
2
3
4
int i = 1;
int *p = &i;
cout<<(void*)p<<" "<<(void*)(p+1)<<endl;
//輸出:0012FF30 0012FF34
1
2
3
4
參考連結

**&陣列名+1:**移動一個陣列大小,與&(陣列名+1)不同,&(陣列名+1)移動到陣列下一個元素;

#include <iostream>
using namespace std;

int main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);//&a+1==> &a+sizeof(a),也就是a[5]的地址,顯然已經超出陣列的界限

cout<<*(a+1)<<endl;//a+1 ==>&a[0]+1==>a[1];
cout<<*(ptr-1)<<endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
(ptr-1)輸出為多少? &a+1不是首地址+1,系統會認為加了一個a陣列,偏移了整個陣列a的大小(也就是5個int的大小)。所以intp=(int*)(&a+1);其實ptr實際是&(a[5]),也就是a+5.原因為何呢? &a是陣列指標,其型別為int()[5];(指向含有5個int的陣列), 而指標加1要根據指標型別加上一定的值,不同型別的指標+1之後增加的大小不同,a是長度為5的int陣列指標,所以要加5sizeof(int),所以p實際是a[5],但是p與(&a+1)型別是不一樣的,這點非常重要,所以ptr-1只會減去sizeof(int*),a,&a的地址是一樣的,但意思就不一樣了,a是陣列首地址,也就是a[0]的地址,&a是物件(陣列)首地址,a+1是陣列下一元素的地址,即a[1],&a+1是下一個物件的地址,即a[5]。

a是陣列首元素的地址;

&a是整個陣列的首地址。二者值一樣,但是意義卻不相同。
陣列名代表整個陣列的時候只有兩種情況:sizeof(陣列名),這裡的陣列名錶示整個陣列。&陣列名,這裡的陣列名錶示整個陣列。(對上例陣列a,sizeof(a)的值為20,表示整個陣列大小。sizeof(a+0)的值為4,因為類似a+0,a+1等陣列名進行了偏移運算,那麼它就代表指向某個元素的指標)
————————————————
版權宣告:本文為CSDN博主「在下能貓」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/qq_23996069/article/details/89309016
3.10 相同內容的資料儲存地址關係
#include <iostream>
using namespace std;

int main()
{
char str[] = "abc";
char str1[] = "abc";

const char str2[] = "abc";
const char str3[] = "abc";

const char *str4 = "abc";
const char *str5 = "abc";

char *str6 = "abc";
char *str7 = "abc";

/*
*陣列 str str1 str2 str3 都是在棧中分配,記憶體中的內容都是"abc"+'/0';
*但是,他們的位置是不同的;
*/
cout << (str == str1) << endl; //==0
cout << (str2 == str3) << endl; //==0

/*
*陣列 str4 str5 str6 str7 也是在棧中分配,他們都指向字串"abc",由於"abc"存放在資料區,
*所以,str4 str5 str6 str7 其實指向同一塊資料區記憶體;
*/
cout << (str4 == str5) << endl; //==1
cout << (str5 == str6) << endl; //==1
cout << (str6 == str7) << endl; //==1
}

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
3.11 記憶體越界
#include <iostream>
extern "C"
{
#include <string.h>
}

using namespace std;

int main()
{
char a;
char *str1 = &a;
//strcpy(str1, "hello"); //!執行出錯; str1 指向一個位元組大小的記憶體,由於複製"hello"需要至少6個位元組,顯然記憶體不夠;
//cout << str1 << endl;

//修改為:
/*
char a[10];
char *str1 = a;
strcpy(str1, "hello");
*/

char *str2="AAA";
// str2[0]='B'; //! str2 指向一個常量,因為是常量,所以不能進行重新賦值;

//修改為:
/*
char str2[]="AAA";
str2[0]='B';
*/
cout<<str2<<endl;
}

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
3.12 指標常量與常量指標
常量指標:就是指向常量的值指標,它指向的內容是不可以修改的;目的是為了防止程式過程中對指標誤操作出現修改常量的錯誤;
指標常量:就是指標是常量,它是不可改變地址的指標,但是,可以對指標指向的內容進行修改;

3.13 this指標
下列關於this指標描述正確的是(A,B)
A. 類的非靜態成員函式才有this指標;
B.類的友元函式,是非成員函式,所以不具有this指標;

3.14 函式指標與指標函式
指標函式:首先它是一個函式,返回型別是一個指標;
函式指標:首先它是一個指標,指向一個函式;

Tips: 像這樣連著的兩個詞,前面的一個通常是修飾部分,中心部分在後面;

指標函式
指標函式,簡單的來說,就是一個返回指標的函式,其本質是一個函式,而該函式的返回值是一個指標。
宣告格式為:
型別識別符號 * 函式名(參數列)
int fun(int x,int y);
1
這種函式應該都很熟悉,其實就是一個函式,然後返回值是一個 int 型別,是一個數值。
接著看下面這個函式宣告:

int *fun(int x,int y);
1
這和上面那個函式唯一的區別就是在函式名前面多了一個*號,而這個函式就是一個指標函式。其返回值是一個 int 型別的指標,是一個地址,一般返回一個地址是危險的,若返回一個區域性變數的地址,該區域性變數在其生命週期結束後,就會被銷燬,此時該地址的內容是不確定

函式指標
與指標函式不同,函式指標 的本質是一個指標,該指標的地址指向了一個函式,所以它是指向函式的指標。所以呼叫時,需要指標函式的入口地址給指標;
我們知道,函式的定義是存在於程式碼段,因此,每個函式在程式碼段中,也有著自己的入口地址,函式指標就是指向程式碼段中函式入口地址的指標。

#include<iostream>
using namespace std;
/*
* 求最大值
* 返回值是int型別,返回兩個整數中較大的一個
*/
int max(int a, int b) {
return a > b ? a : b;
}

/*
* 求最小值
* 返回值是int型別,返回兩個整數中較小的一個
*/
int min(int a, int b) {
return a < b ? a : b;
}

int (*f)(int, int); // 宣告函式指標,指向返回值型別為int,有兩個引數型別都是int的函式
//函式指標,只需要宣告,不需要實現;
int main(int argc, char* argv[])
{

f = max; // 函式指標f指向求最大值的函式max
int c = (*f)(1, 2);

printf("The max value is %d \n", c);

f = min; // 函式指標f指向求最小值的函式min
c = (*f)(1, 2);

printf("The min value is %d \n", c);

printf("------------------------------ End\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
多個函式指標使用
#include<iostream>
using namespace std;
int add1(int x,int y)
{
return x+y;
}
int add2(int x,int y)
{
return x+y;
}

int main(int argc, char* argv[])
{
int (*p[2])(int,int); //!此處定義了一個指標陣列,陣列存放兩個指標,指向不同的函式;
p[1]=add1;
p[2]=add2;

cout<<"p[1](1,2)="<<p[1](1,2)<<endl;
cout<<"p[2](3,4)="<<p[2](3,4)<<endl;
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
3.15 typedef 用於函式指標
#include<iostream>
using namespace std;
int add1(int x,int y)
{
return x+y;
}

typedef int (*pFun)(int, int);//! typedef 用於函式指標
//! 這裡的pFun是一個使用typedef 定義的資料型別,表示一個函式指標
//!其引數有兩個,都是int型,返回值也是int型;

int (*pFun2)(int,int);// 與上式作對比
int main(int argc, char* argv[])
{
//------------------------------------------------
pFun fun=add1; //! 用 pFun型別定義一個fun,然後呼叫函式
cout<<fun(2,3)<<endl;

//------------------------------------------------

pFun2=add1;
cout<<(*pFun2)(3,4)<<endl;
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
3.16 野指標
野指標,不是指向NULL的指標,而是指向"垃圾"記憶體的指標.其原因主要是:指標變數沒有被初始化,或指標p被free或者delete之後,沒有置為NULL;
為了防止出現野指標,所以應該在初始化的將指標置空;

3.17 有了malloc/free 為什麼還需要new/delete
new/delete和malloc/free的區別

malloc和free是庫函式,而new和delete是C++運算子;
new自己計算需要的空間大小,比如’int * a = new,malloc需要指定大小,例如’int * a = malloc(sizeof(int))’;
opeartor new /operator delete可以過載,而malloc不行
malloc能夠直觀地重新分配記憶體
使用malloc分配的記憶體後,如果在使用過程中發現記憶體不足,可以使用realloc函式進行記憶體重新分配實現記憶體的擴充。realloc先判斷當前的指標所指記憶體是否有足夠的連續空間,如果有,原地擴大可分配的記憶體地址,並且返回原來的地址指標;如果空間不夠,先按照新指定的大小分配空間,將原有資料從頭到尾複製到新分配的記憶體區域,而後釋放原來的記憶體區域。
對於自定義物件,物件的消亡之前要自動執行解構函式,由於malloc/free是庫函式而不是運算子,不在編譯器控制許可權之內,不能夠把執行建構函式和解構函式的任務強加於malloc/free。
3.17 記憶體越界
#include <iostream>
using namespace std;
extern "C"
{
#include <string.h>
}

int main(int argc, char *argv[])
{
char *strp;
char *str = "test";

/*記憶體越界*/
// strp=new char[strlen(str)];
// strcpy(strp,str);//!記憶體越界;由於str的長度還有一個'/0';

strp = new char[strlen(str) + 1]; //新建記憶體容量,應該+1
strcpy(strp, str);

cout << str << endl;
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
3.18 動態記憶體傳遞
#include <iostream>
extern "C"
{

#include <string.h>
}
using namespace std;
char *getMemory_(int num)
{
char *p = new char[sizeof(char) * num];
return p;
}
void getMemory(char *p, int num)
{
p = new char[sizeof(char) * num];
cout << sizeof(p) << endl;
}
int main(int argc, char *argv[])
{
char *strp = nullptr;
int num = 10;

//-------------------------------
getMemory(strp, num);
//cout<<strlen(strp)<<endl;;//!出錯,因為呼叫getMemory函式體內的p實際上是main函式中strp的備份,變數在getMemory()
//!函式棧中的一個備份,因為編輯器總是為函式的每一個引數製作臨時變數,因此在getMemory()申請
//!堆記憶體,但是返回main函式時,str還是NULL,並不指向那塊記憶體,所以呼叫此句會出現記憶體錯誤;

//-改正為:在子函式中生成一段記憶體,並將指向這段記憶體的指標返回;
char *strp_=getMemory_(1);
cout<<strlen(strp_)<<endl;
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
對於記憶體洩露可以透過一下三種方法進行傳遞動態記憶體:

採用指向指標的指標,可以將str的地址傳給函式GetMemory();
傳遞指標的引用;
使用返回值來傳遞動態記憶體;
#include <iostream>
extern "C"
{

#include <string.h>
}
using namespace std;

char *getMemory1(int num)
{
char *p = new char[sizeof(char) * num];
return p;
}
void getMemory2(char *&p, int num) //! 傳遞指標的引用;
{
p = new char[sizeof(char) * num];
}

void getMemory3(char **p, int num)
{
*p = new char[sizeof(char) * num];
}
int main(int argc, char *argv[])
{

char *strp1 = getMemory1(20);
char *strp2 = nullptr, *strp3 = nullptr;
getMemory2(strp2, 20);
getMemory3(&strp3, 20);

strcpy(strp1, "getMemory 1");
strcpy(strp2, "getMemory 2");
strcpy(strp3, "getMemory 3");

cout << "strp1=" << strp1 << endl;
cout << "strp2=" << strp2 << endl;
cout << "strp3=" << strp3 << endl;
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
4.0 易錯
#include <iostream>
#include <string>
using namespace std;

int main(int agrv, char *agrc[])
{
char *str="hello world!";
cout<<str<<endl; //==>hello world!,輸出的不是地址 !!!
cout<<*str<<endl; //!==>h 解索引輸出的是首地址對應的記憶體內容;

string str1="test string";
cout<<str1<<endl;
string *str2=&str1;
cout<<*str2<<endl;
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
4.0.1 string 與char *的區別
定義:
string:string是STL當中的一個容器,對其進行了封裝,所以操作起來非常方便。
char*:char *是一個指標,可以指向一個字串陣列,至於這個陣列可以在棧上分配,也可以在堆上分配,堆得話就要你手動釋放了。
區別:
string的記憶體管理是由系統處理,除非系統記憶體池用完,不然不會出現這種記憶體問題。
char *的記憶體管理由使用者自己處理,很容易出現記憶體不足的問題。
4.1 不使用庫函式將數字轉換成char
要想將數字轉換成字元,將每一個位置上的數字+'0',就可以將此位置上的數字從int轉換為char;
注意:數字只能取正數,不能取負數;
0+‘0’=‘0’;
1+‘0’=‘1’;

9+‘0’=‘9’;

所以解法:

首先判斷要轉換的數字的正負性;負數取絕對值;
提取每一位,然後加'0',轉換成char;
負數前面加'-';
字串的最後需要加上'\0'
#include <iostream>
extern "C"
{

#include <string.h>
}
using namespace std;
void int2char(int n, char *str)
{
char buff[10] = "";
int i = 0;
int len = 0;

int temp = (n > 0) ? n : -n;

if (str == nullptr)
{
return;
}

while (temp)
{
buff[i++] = temp % 10 + '0'; //提取每一位數字;
temp = temp / 10; //下一位;

//例如:temp=123
//1. temp%10=12...3,temp%10=3;temp/10=12;temp=12;
//2. temp%10=1...2;temp%10=2;temp/10=2;temp=1;
//1.temp%10=0...1;temp%10=1;temp/10=0;temp=0;退出

//此時buff中儲存的是倒敘:321;
}

cout<<i<<endl;
len = n < 0 ? ++i : i; //如果n是負數,則需要新增一位來儲存'-'號;
str[i] = '\0'; //輸出字串,末尾結束符;'\0'
cout<<len<<endl;
while (1)
{
i--;
cout<<i<<endl;
if (buff[len - i - 1] == '\0')
{
break;
}

str[i] = buff[len - i - 1]; //倒敘轉正序;
}
if (i == 0)
{
str[i] = '-';
}
}
int main(int argc, char *argv[])
{
char p[10];
int2char(-123, p);
cout << p << endl;

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
4.2 字元轉數字
#include <iostream>
extern "C"
{

#include <string.h>
}
using namespace std;
int str2int(const char *str)
{
int temp=0;
const char *ptr=str;

if(*str=='-'||*str=='+') //如果第一字元是正負號,則移動到下一個字元;
{
str++;
}
while (*str!='0')//==>while (*str!=0) //!'\0'代表ASCII為0的字元,從ASCII表上可以知道,ASCII為0,是一個空字元,不可見.
{
if(*str<'0'||*str>'9') //如果字元不是數字,則退出
{
break;
}
temp=temp*10+(*str-'0');//將字元轉換成數字==>-'0';
str++;

cout<<temp<<endl;
}
return temp;
}
int main(int argc, char *argv[])
{
int num=str2int("123");
cout<<num<<endl;
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
4.3 strcpy()的實現
#include <iostream>
using namespace std;
void strCopy(char *strDest, const char *str)
{
if ((strDest == NULL) || (str == NULL))
{
return;
}

const char *desstr = strDest; //儲存目標首地址;
while ((*strDest++ = *str++) != '\0')
{
}
return;
}
int main(int agrv, char *agrc[])
{
char *str1 = "hello world";
char str2[20];
strCopy(str2, str1);
cout << str2 << endl;
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
4.4 尋找子串
#include <iostream>
extern "C"
{
#include <string.h>
}
using namespace std;

char *comonString(char *str1, char *str2)
{
int i, j;
char *longstr, *shorstr;
if ((str1 == NULL) || (str2 == NULL))
{
return NULL;
}
if (strlen(str1) <= strlen(str2))
{
shorstr = str1;
longstr = str2;
}
else
{
shorstr = str2;
longstr = str1;
}
if (strstr(longstr, shorstr) != NULL) //!採用strstr()在一個字串中,尋找子串;
//! 如果在長的子串中能尋到短的子串,返回短子串;
{
return shorstr;
}

char *substr = new char[strlen(shorstr) + 1]; //用於儲存子串
for (i = strlen(shorstr) - 1;i > 0; i--)
{
cout<<"strlen(shorstr) - 1="<<strlen(shorstr) - 1<<endl;
for (j = 0; j < strlen(shorstr) - i; j++)
{
cout<<"strlen(shorstr) - i="<<strlen(shorstr) - i<<endl;
memcpy(substr, &shorstr[j], i); //將短字串的一部分複製到substr,其長度逐漸減小;
substr[i] = '\0';
if (strstr(longstr, substr) != NULL) //在longstr中尋找子串;
{
return substr;
}
}
}
return NULL;
}
int main(int agrv, char *agrc[])
{

char *str1="find test";
char *str2="test_";
//cout<<strlen(str2)<<endl; //字串長度:4;
char *common=comonString(str1,str2);
cout<<common<<endl;
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
4.5 常用c字串操作
此部分內容來自:版權宣告:本文為CSDN博主「芮小譚」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/tanrui519521/article/details/81162267

strlen()
size_t strlen( const char* str)
1
功能:計算字串長度,不包含’\0’
返回值:返回字串的字元數;

strlen() 函式計算的是字串的實際長度,遇到第一個’\0’結束;
引數指向的字串必須以 ’ \0 ‘結束
函式返回值一定是size_t ,是無符號的
如果你只定義沒有給它賦初值,這個結果是不定的,它會從首地址一直找下去,直到遇到’\0’停止
sizeof返回的是變數宣告後所佔的記憶體數,對於字串會包含’\0’所佔用的記憶體,不是實際長度,此外sizeof不是函式,僅僅是一個運算子,strlen()是函式;
#include <iostream>
extern "C"
{
#include <string.h>
}
using namespace std;
int main(int agrv, char *agrc[])
{
char str1[] = "test";
cout << sizeof(str1) << endl; //==>5 因為'\0'佔有記憶體;
cout << strlen(str1) << endl; //==>4 不包含'\0'長度;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
strcpy()
char* strcpy(char* dest,char* src)
1
功 能: 將引數src字串複製至引數dest所指的地址
返回值: 返回引數dest的字串起始地址

源字串必須以’\0’結束
會將源字串的’\0’複製到目標空間
目標空間必須可變
如果引數dest所指的記憶體空間不夠大,可能會造成緩衝溢位的錯誤情況,在編寫程式時需特別留意,或者用strncpy()來取代
strncpy()
char* strncpy(char* dest,const char* src,size_t num)
1
功能:複製src字串的前num個字元至dest
返回值:dest字串起始地址
說明:

如果src字串長度小於num,則複製完字串後,在目標後追加0
strncpy不會向dest追加’\0’
src和dest所指的記憶體區域不能重疊,且dest必須有足夠的空間放置n個字元
strcat()
char* strcat(char* dest,const char* src)
1
功能: 字串拼接
返回值:返回dest字串起始地址
說明:

源字串必須’\0’結束
目標空間必須可修改
strcat() 會將引數src字串複製到引數dest所指的字串尾部
dest最後的結束字元’\0’會被覆蓋掉,並在連線後的字串的尾部再增加一個’\0’
dest與src所指的記憶體空間不能重疊,且dest要有足夠的空間來容納要複製的字串
strncat()
char* strncat (char* dest,const char* src,size_t num)
1
功能:將n個字元追加到字串結尾
返回值:返回dest字串的起始地址
說明:

strncat將會從字串src的開頭複製n個字元到dest字串尾部
dest要有足夠的空間來容納要複製的字串
如果n大於字串src的長度,那麼僅將src全部追加到dest的尾部
strncat會將dest字串最後的’\0’覆蓋掉,字元追加完成後,再追加’\0’
6.strcmp()

int strcmp (const char* str1,const char* str2)
1
功能:字串比較
返回值:若引數s1和s2字串相同則返回0,s1若大於s2則返回大於0的值,s1若小於s2則返回小於0的值
說明:

判斷兩個字串大小1)ASII碼 2)長度
區分大小寫比較的,如果希望不區分大小寫進行字串比較,可以使用stricmp函式
strncmp()
int strncmp(const char* str1,const char* str2,size_t num)
1
功能:指定長度比較

8.strstr()

char* strstr(const char* str,const char* substr)
1
功能:檢索子串在字串中首次出現的位置
返回值:返回字串str中第一次出現子串````的地址;如果沒有檢索到子串,則返回NULL

5.位制轉換
在32位計算機中,int與float 均佔4個位元組,double佔有8個位元組;一般情況下,在c++中,使用cout輸出是沒有問題的,但是採用printf()會涉及位制轉換導致讀取錯誤;

printf()會根據說明符"%f".編譯器認為引數應該是double型別(在printf函式中,float會自動轉換成為double),因此會從棧中讀出8個位元組.類似地,當printf()說明符為"%d"時,編譯器認為引數應該是int型別,因此會從棧中讀出4個位元組;

#include<iostream>
using namespace std;
int main()
{

printf("%f\n",5);//可能存在記憶體越界的錯誤:引數5是一個int型,所以在棧中分配了4個位元組的記憶體用於存放引數5,然後在printf從棧中讀取8個位元組;
printf("%d\n",5.01);//出錯!引數5.01佔用8個位元組,讀取時讀取了4個位元組,所以記憶體越界;
printf("%f\n",5.01);

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
6.1 private 資料型別
private:用來指定私有成員。一個類的私有成員,不論是成員變數還是成員函式,都只能在該類的成員函式內部才能被訪問。

#include <iostream>
using namespace std;
class A
{
private:
void print()
{
cout << "private print" << endl;
}
public:
void print2()
{
print();//一個類的私有成員,不論是成員變數還是成員函式,都只能在該類的成員函式內部才能被訪問。
cout<< "public print" << endl;
}
};

int main()
{
A a;
a.print2();
//a.print(); 無法呼叫
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
6.2 初始化的坑
#include <iostream>
using namespace std;
class A
{
private:
int i;
int j;
public:
A(int x):j(x),i(j){}
void print()
{
cout<<"i="<<i<<"\n"<<"j="<<j<<endl;
}
};
int main()
{
A a(10);
a.print();//==>i=0;j=10;

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
此處初始化完成後,i!=10;(錯誤認為:先用10對j初始化,然後再用j對i初始化,所以兩者的結果都是10 )
初始化成員列表的初始化順序與變數宣告的順序一致:而不是按照出現在初始化列表中順序;
這裡成員i比成員j先宣告,因此成員i先被初始化,而此時j未被初始化,j是一個隨機值,故i的值也是隨機值;(==0?)

6.3 解構函式與建構函式是否可以被過載
建構函式:可以過載;
解構函式:不可以,解構函式只能有一個;

6.4 建構函式中explict與普通函式的區別
#include <iostream>
using namespace std;
class Test1
{
private:
int num;
public:
Test1(int n):num(n){}
};
class Test2
{
private:
int num;
public:
explicit Test2(int n):num(n){} //explicit(明確的) 顯示建構函式
};
int main()
{
Test1 test1(10);
Test1 test2=20; //隱式呼叫其建構函式

Test2 test3(30);
//Test2 test4=40; //編譯錯誤,不能透過隱式轉換呼叫其建構函式;
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
6.5 explicit 的作用
#include <iostream>
#include <string>
using namespace std;
class Number
{
public:
string type;
Number() : type("void") {}
explicit Number(short) : type("short") {}
Number(int) : type("int") {}
};

void show(const Number &n) { cout << n.type << endl; }
int main()
{
short s = 42; //'='表示賦值,即隱式轉換
// Number num=s; //採用隱式呼叫;
// show(num); //!輸出==>int
show(s); //!輸出==>int
//!原因如下:show(s)的s為short型別,其值為42,因此會首先檢查引數為short的建構函式是否被隱式轉換.
//!由於short型別的建構函式被宣告為explict 所以不可以被隱式轉換;
//!42 會被自動轉換成int 型別;
//!檢查int型別是否可以被隱式轉換,int型別的建構函式沒有explicit 宣告,所以可以進行隱式轉換,因此輸出int

short ss = 45;
Number num2(ss);
show(num2); //輸出==>short
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
6.6 繼承類析構方式
#include <iostream>
#include <string>
using namespace std;
class A
{
private:
int a;
public:
A(int aa);
~A();
};
A::A(int aa):a(aa)
{
}
A::~A()
{
cout<<"destructor A"<<a<<endl;
}
class B:public A
{
private:
int b;
public:
B(int aa,int bb);
~B();
};

B::B(int aa=0,int bb=0):A(aa),b(bb)
{
}

B::~B()
{
cout<<"destructor B"<<b<<endl;
}

int main()
{
B obj(5),obj2(6,7);//==>輸出
/* destructor B7
destructor A6
destructor B0
destructor A5
*/
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
41
42
43
44
45
46
所以解構函式執行不光只執行繼承類的解構函式,還要執行基類的解構函式!!

7.類的繼承
類的成員資料型別;

protected:protectd成員資料或者成員函式,只能被本類或本類派生類中的成員函式訪問;
private:private成員資料或者成員函式,只能被本類中的成員函式訪問;
public :public成員資料或者成員函式,既可以被本類的成員函式訪問,也可以被類外的函式訪問
類繼承方式:

public 繼承:基類的中的**公有成員***(成員:成員函式與成員資料)和保護成員***,在派生類中的訪問許可權不變,仍然是公有成員和保護成員;私有成員,在繼承類中訪問許可權是不可訪問;
protected 繼承:基類的中的公有成員(成員:成員函式與成員資料)和保護成員,在派生類中的訪問許可權變為保護成員;基類中公有成員和保護成員在派生類內部都是可以訪問的;私有成員,在繼承類中訪問許可權是不可訪問;
private 繼承: 基類的中的公有成員(成員:成員函式與成員資料)和保護成員,在派生類中的訪問許可權變為私有成員;私有成員,在繼承類中訪問許可權是不可訪問;
由此可見,無論何種繼承方式,基類的private成員,均是不可以訪問;
基類中的成員 在公有派生中的訪問屬性 在保護派生中的訪問屬性 在私有派生中的訪問屬性
私有成員 不可訪問 不可訪問 不可訪問
保護成員 保護 保護 私有
公有成員 公有 保護 私有
#include <iostream>
#include <string>
using namespace std;
class A
{
private:
int a;
void printf_private()
{
cout << a << b << c << endl;
}

protected:
int b;
void printf_protected()
{
cout << a << b << c << endl;
}

public:
int c;
A(int a_, int b_, int c_) : a(a_), b(b_), c(c_){};

void printf_public()
{
cout << a << b << c << endl;
}
};
class B : public A
{
public:
B(int a_, int b_, int c_) : A(a_, b_, c_) {}
void B_print_public()
{
printf_protected();
printf_public();
//cout<<a<<endl; //public 繼承後,基類的私有成員或成員函式均是不能訪問的;
cout << b << endl;
cout << c << endl;
}
};
class C : protected A
{
public:
C(int a_, int b_, int c_) : A(a_, b_, c_) {}
void C_print_public()
{
printf_protected();
printf_public();

//cout<<a<<endl;//protected 繼承後,基類的私有成員或成員函式均是不能訪問的;
cout << b << endl;
cout << c << endl;
}
};
class D : private A
{
public:
D(int a_, int b_, int c_) : A(a_, b_, c_) {}
void D_print_public()
{
printf_protected();
printf_public();
//cout<<a<<endl; //private 繼承後,基類的私有成員或成員函式均是不能訪問的;
cout << b << endl;
cout << c << endl;
}
};
int main()
{
B b_public(1, 2, 3);
C c_protected(1, 2, 3);
D d_private(1, 2, 3);

b_public.printf_public();
b_public.B_print_public();
cout << b_public.c << endl; //public 繼承的public成員資料,可以被訪問

c_protected.C_print_public();
//c_protected.printf_public(); //保護繼承,只能在類的成員函式中訪問
//cout<< c_protected.c<<endl; //保護繼承,基類的public成員,訪問屬性為protected,只能在類的成員函式內部訪問;

d_private.D_print_public();
//d_private.printf_public(); //私有繼承,只能在類的成員函式中訪問;
//cout<<d_private.c<<endl; //私有繼承,基類的public成員,訪問屬性為private,只能在類的成員函式內部訪問;
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
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
7.1 繼承中建構函式呼叫方式
#include <iostream>
#include <string>
using namespace std;

void prinln(const std::string &msg)
{
cout << msg << endl;
}
class Base
{
public:
Base()
{
prinln("Base::Base()");
virt();
}
void f()
{
prinln("Base::f()");
virt();
}
virtual void virt()
{
prinln("Base::virt()");
}
};

class Derived : public Base
{
public:
Derived()
{
prinln("Derived::Derived()");
virt();
}

virtual void virt()
{
prinln("Derived::virt()");
}
};

int main()
{
Derived d;
Base *pB = &d;
pB->f();
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
41
42
43
44
45
46
47
48
49
構造Derived 物件d.首先呼叫Base的建構函式,然後呼叫Derived的建構函式.在Base類的建構函式中,有呼叫了虛擬函式virt(),此時虛擬機器制還沒有開始作用(因為是在建構函式中),所以執行的是Base類的vitr()函式.同樣,在Derived類的建構函式中,執行的是Derived類的virt函式;

透過Base類的指標pB訪問Base類的公有成員函式f(),f()函式又呼叫了虛擬函式virt(),這裡出現了多型,由於指標pB是指向Derived類物件的,因此實際執行的Derived類的virt()成員;

所以,對於繼承類說,如果是建構函式, 則首先構造基類的建構函式,然後執行繼承類的建構函式;對於解構函式,則是先析構繼承類,然後析構基類;,

7.2 虛擬函式與純虛擬函式的區別
類裡面如果宣告瞭虛擬函式,這個函式是實現的,哪怕是空實現,它的作用就是能讓這個函式在它的子類中裡面可以覆蓋,這樣編譯器就可以使用後期繫結來達到多型了.虛擬函式只是一個介面,是個函式的宣告而已,他要留到子類裡去實現;
虛擬函式在子類裡面可以不過載,但純虛擬函式必須要到子類去實現;
所以虛擬函式的目的是為了實現多型,純虛擬函式的作用是為了預留一個介面和多型的實現;
帶純虛擬函式的類叫做虛基類,這種基類不能直接定義物件,而只有被繼承,並重寫了其虛擬函式後,才能使用,但可以定義其指標或引用 ;
7.2.1 虛擬函式
#include<iostream>
#include<cmath>
using namespace std;
const double PI=3.1415926;
class Circle
{
protected:
double r;
public:
Circle(double rad)
{
r=rad;
}
double Peri()
{
return 2*PI*r;
}
virtual double Area() //基類虛擬函式
{
return PI*r*r;
}
};
class Cyclinder:public Circle
{
double h;
public:
Cyclinder(double rad,double heigth):Circle(rad)
{
h=heigth;
}
double Area() //!==>儘管重新定義了Area(),但新定義的Area繼承了基類的虛特性,成為虛擬函式,具有虛擬函式的特性; ==> virtual double Area()
{
return Peri()*h;
}
};
class Cone:public Circle
{
double h;
public:
Cone(double rad,double heigth):Circle(rad)
{
h=heigth;
}
double Area() //!==>儘管重新定義了Area(),但新定義的Area繼承了基類的虛特性,成為虛擬函式,具有虛擬函式的特性; ==> virtual double Area()
{
return PI*r*sqrt(r*r+h*h);
}
};
void fun(Circle *pb)
{
cout<<pb->Area()<<endl;
}
int main()
{
Cyclinder cy(3,5);
Cone cn(3,5);
cout<<"圓柱體的側面積是:";
fun(&cy);
cout<<"圓錐體的側面積是:";
fun(&cn);
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
由於在基類Circle中將Area函式定義為虛擬函式(A行),在其派生類Cylinder和Cone中,儘管也重新定義了Area(B行和C行),但新定義的Area繼承了基類的虛特性,成為虛擬函式,具有虛擬函式的特性.這樣,當基類指標指向派生類物件,並且用基類指標呼叫虛擬函式(pb->Area())時,執行的是派生類中新定義的同名函式,即分別求Cylinder和Cone的側面積;

7.2.2 純虛擬函式
#include<iostream>
#include<cmath>
using namespace std;
const double PI=3.1415926;
class Circle
{
protected:
double r;
public:
Circle(double rad)
{
r=rad;
}
double Peri()
{
return 2*PI*r;
}
virtual double Area()
{
return PI*r*r;
}
virtual double volume()=0; //純虛擬函式,預留介面,保證多型性

};
class Cyclinder:public Circle
{
double h;
public:
Cyclinder(double rad,double heigth):Circle(rad)
{
h=heigth;
}
double Area()
{
return Peri()*h;
}
virtual double volume()
{
return Circle::Area()*h; //衝突與支配,否則會執行派生類中的Area()
}
};
class Cone:public Circle
{
double h;
public:
Cone(double rad,double heigth):Circle(rad)
{
h=heigth;
}
double Area()
{
return PI*r*sqrt(r*r+h*h);
}
virtual double volume()
{
return Circle::Area()*h/3; //衝突與支配,否則會執行派生類中的Area()
}
};
void fun(Circle *pb)
{
cout<<pb->Area()<< " "<<pb->volume()<<endl;
}
int main()
{
//Circle circle(5); //!抽象類不能定義物件;
Circle *circle_ptr; //!但可以定義指標與引用;

Cyclinder cy(3,5);
Cone cn(3,5);
cout<<"圓柱體的側面積和體積是:";
fun(&cy);
cout<<"圓錐體的側面積和體積是:";
fun(&cn);
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
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
8.1 STL
#include <iostream>
#include <vector>
#include <thread>
using namespace std;
int main()
{
vector<int> array;
for (int i =1; i < 4; i++)
{
array.push_back(i);
}

for(vector<int>::size_type i=array.size()-1;i>=0;i--)
{
cout<<array[i]<<endl;
this_thread::sleep_for(chrono::seconds(1));
}
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
此程式是一個死迴圈!!原因在與vector<int>::size_type的型別;
vector<int>::size_type<==typedef _SIZT size_type;<==typedef unsigned int size_t;所以size_type 是一個unsigned int 型別,是一個**>=0**的數,所以此迴圈會導致死迴圈:

#include <iostream>
#include <vector>
#include <thread>
using namespace std;
int main()
{
vector<int> array;
for (int i = 1; i < 4; i++)
{
array.push_back(i);
}

// for(vector<int>::size_type i=array.size()-1;i>=0;i--)
// {
// cout<<array[i]<<endl;
// this_thread::sleep_for(chrono::seconds(1));
// }
for (vector<int>::size_type j = array.size(); j > 0; j--)
{
cout << array[j - 1] << endl;
this_thread::sleep_for(chrono::seconds(1));
}

for (int k = array.size()-1; k >= 0; k--)
{
cout << array[k] << endl;
this_thread::sleep_for(chrono::seconds(1));
}
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
8.2 STL 刪除某一個元素後,會自動移項
/*
*刪除程式中所有的2;
*/

#include <iostream>
#include <vector>
#include <thread>
using namespace std;
int main()
{
vector<int> array;
array.push_back(1);
array.push_back(2);
array.push_back(2);
array.push_back(3);

for (vector<int>::iterator itor = array.begin(); itor != array.end(); itor++)
{
if (2 == *itor)
{
array.erase(itor); //透過此方法只會刪除一個2;
//! 這是因為每次呼叫"array.erase(itor);",被刪除的元素之後的內容就會自動往前移動,
//!導致漏項,應該在刪除一項後使itor--,使之從已經前移的下一個元素起繼續遍歷;
itor--; //新增此句,才不會導致漏項!!
}
}

for (auto &index : array)
{
cout << index << endl;
}
}

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
array.erase(itor); //透過此方法只會刪除一個2;
//! 這是因為每次呼叫"array.erase(itor);",被刪除的元素之後的內容就會自動往前移動,
//!導致漏項,應該在刪除一項後使itor–,使之從已經前移的下一個元素起繼續遍歷;

8.3 list 與vector的區別
vector 與陣列類似,它擁有一段連續的記憶體空間,並且起始地址不變,因此他能非常好的支援隨機存取(使用[]運算子訪問其中的元素).但是,由於它的記憶體空間是連續的,所以在進行插入操作和刪除操作會造成記憶體的塊複製,當記憶體空間不夠時,需要重新申請一塊足夠大的記憶體並進行記憶體的複製;

list是由資料結構中的雙向連結串列實現的,因此它的記憶體空間可以是不連續的,所以插入與刪除效率很高;
————————————————

版權宣告:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本宣告。

原文連結:https://blog.csdn.net/Destiny_zc/article/details/118532083

相關文章