Android開發的C++基礎Notes(轉)

轉自:http://cfanr.cn/2017/07/28/Android-dev-cpp-foundation-knowlege-notes/
1. 局部變量和全局變量初始化問題

當局部變量被定義時,系統不會對其初始化,您必須自行對其初始化。定義全局變量時,系統會自動初始化爲下列值,int - 0, char- ‘\0’,float - 0 等

2. 定義常量方式

C++中有兩種方式, C++ 常量 | 菜鳥教程

  • 使用宏定義#define預處理器(不需要分號,不需要聲明類型
    形式:#define identifier value,如 # define LENGTH 10
  • 使用 const 關鍵字
    形式:const type variable = value;
    常量名定義和 Java 一樣,最好使用大寫字母形式,同時注意常量定義必須有值,也不能被重新賦值(不然就不叫常量了)

兩種定義方式的區別:

  • 類型和安全檢查不同
    宏定義是字符替換,沒有數據類型的區別,同時這種替換沒有類型安全檢查,可能產生邊際效應等錯誤;const 常量是常量的聲明,有類型區別,需要在編譯階段進行類型檢查
  • 編譯器處理不同
    宏定義是一個”編譯時”概念,在預處理階段展開,不能對宏定義進行調試,生命週期結束與編譯時期;const常量是一個”運行時”概念,在程序運行使用,類似於一個只讀行數據
  • 存儲方式不同
    宏定義是直接替換,不會分配內存,存儲與程序的代碼段中;const常量需要進行內存
  • 定義域不同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void f1 ()
    {
    #define N 12
    const int n 12;
    }
    void f2 ()
    {
    cout<<N <<endl; //正確,N已經定義過,不受定義域限制
    cout<<n <<endl; //錯誤,n定義域只在f1函數中
    }
  • 定義後能否取消
    宏定義可以通過#undef來使之前的宏定義失效;const 常量定義後將在定義域內永久有效

    1
    2
    3
    4
    5
    6
    7
    8
    void f1()
    {
    #define N 12
    const int n = 12;
    #undef N //取消宏定義後,即使在f1函數中,N也無效了
    #define N 21//取消後可以重新定義
    }
  • 是否可以做函數參數
    宏定義不能作爲參數傳遞給函數
    const常量可以在函數的參數列表中出現

另外,還有 typedef 的關鍵字,用來對一個類型取一個新名字,目的是爲了使源代碼更易於閱讀和理解。如在 JNI 的頭部文件會看到這種

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifdef HAVE_INTTYPES_H
# include <inttypes.h> /* C99 */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#else
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#endif

3. C++修飾符類型

數據類型修飾符:signed, unsigned, long, short

  • 修飾整形:signed, unsigned, long, short
  • 修飾字符型:signed, unsigned
  • 修飾雙精度性:long
  • long 或 short 的修飾符前綴:signed, unsigned
    C++ 速記符號聲明無符號短整數或無符號長整數,可以使用 unsigned、short 或 unsigned、long,其中 int 是隱含的

類型限定符
const:對象在程序執行期間不能被修改改變。
volatile:修飾符 volatile 告訴編譯器,變量的值可能以程序未明確指定的方式被改變。
restrict:由 restrict 修飾的指針是唯一一種訪問它所指向的對象的方式。只有 C99 增加了新的類型限定符 restrict。

4. 存儲類

1)static 存儲類
static 存儲類指示編譯器在程序的生命週期內保持局部變量的存在,而不需要在每次它進入和離開作用域時進行創建和銷燬。因此,使用 static 修飾局部變量可以在函數調用之間保持局部變量的值。static 修飾符也可以應用於全局變量。當 static 修飾全局變量時,會使變量的作用域限制在聲明它的文件內。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
// 函數聲明
void func(void);
static int count = 4; /* 全局變量 */
int main()
{
while(count--)
{
func();
}
return 0;
}
// 函數定義
void func( void )
{
static int i = 5; // 局部靜態變量
i++;
std::cout << "變量 i 爲 " << i ;
std::cout << " , 變量 count 爲 " << count << std::endl;
}

輸出爲:

1
2
3
4
變量 i 爲 6 , 變量 count 爲 3
變量 i 爲 7 , 變量 count 爲 2
變量 i 爲 8 , 變量 count 爲 1
變量 i 爲 9 , 變量 count 爲 0

2)extern 存儲類
extern 存儲類用於提供一個全局變量的引用,全局變量對所有的程序文件都是可見的。當您使用 ‘extern’ 時,對於無法初始化的變量,會把變量名指向一個之前定義過的存儲位置。
當您有多個文件且定義了一個可以在其他文件中使用的全局變量或函數時,可以在其他文件中使用 extern 來得到已定義的變量或函數的引用。可以這麼理解,extern 是用來在另一個文件中聲明一個全局變量或函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//main.cpp
#include <iostream>
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
//support.cpp
#include <iostream>
extern int count;//聲明已經在main.cpp 中定義的 count
void write_extern(void)
{
std::cout << "Count is " << count << std::endl;
}

結果爲:Count is 5

3)mutable存儲類
mutable 說明符僅適用於類的對象。它允許對象的成員替代常量。也就是說,mutable 成員可以通過 const 成員函數修改。

4)thread_local 存儲類
使用 thread_local 說明符聲明的變量僅可在它在其上創建的線程上訪問。 變量在創建線程時創建,並在銷燬線程時銷燬。 每個線程都有其自己的變量副本。
thread_local 說明符可以與 static 或 extern 合併。
可以將 thread_local 僅應用於數據聲明和定義,thread_local 不能用於函數聲明或定義。

5. 運算符

a. 位運算符
位運算符有點忘記了,做下筆記記錄下

1)按位或(OR):p | q
兩個數的相應二進制位,只要有1,則爲1
2)按位與(AND):p & q
兩個數的相應二進制位,只有同爲1,才爲1
3)按位異或(XOR):p ^ q
兩個數的相應二進制位,同則爲0,不同爲1
4)取反(NOT):~p
二進制位,1變爲0 ,0變爲1
5)移位
左移:<< ,右移:>>

b. 雜項運算符
1)sizeof——sizeof 運算符返回變量的大小。例如,sizeof(a) 將返回 4,其中 a 是整數。
2)Condition ? X : Y——條件運算符。如果 Condition 爲真 ? 則值爲 X : 否則值爲 Y。
3), ——逗號運算符會順序執行一系列運算。整個逗號表達式的值是以逗號分隔的列表中的最後一個表達式的值。
4).(點)和 ->(箭頭)—— 成員運算符用於引用類、結構和共用體的成員。
5)Cast —— 強制轉換運算符把一種數據類型轉換爲另一種數據類型。例如,int(2.2000) 將返回 2。
6)& —— 指針運算符 & 返回變量的地址。例如 &a; 將給出變量的實際地址。
7) —— 指針運算符 指向一個變量。例如,*var; 將指向變量 var。

6. 循環

C++有 goto 語句;
C++ 程序員偏向於使用 for(;;) 結構來表示一個無限循環;
Switch 語句裏面的判斷類型,必須是一個整型或枚舉類型,或者是一個有單一轉換函數將其轉換爲整型或枚舉類型的 class 類型;

7. 函數

C++需要在調用函數頂部聲明函數,Java 不需要;

函數參數:

如果函數要使用參數,則必須聲明接受參數值的變量。這些變量稱爲函數的形式參數。形式參數就像函數內的其他局部變量,在進入函數時被創建,退出函數時被銷燬。

當調用函數時,有兩種向函數傳遞參數的方式:
1)傳值調用——該方法把參數的實際值複製給函數的形式參數。在這種情況下,修改函數內的形式參數對實際參數沒有影響。
2)指針調用——該方法把參數的地址複製給形式參數。在函數內,該地址用於訪問調用中要用到的實際參數。這意味着,修改形式參數會影響實際參數。
3)引用調用——該方法把參數的引用複製給形式參數。在函數內,該引用用於訪問調用中要用到的實際參數。這意味着,修改形式參數會影響實際參數。

默認情況下,C++ 使用傳值調用來傳遞參數。一般來說,這意味着函數內的代碼不能改變用於調用函數的參數。

Lambda函數與表達式
表達式形式:[capture](parameters)->return-type{body}

注意:
宏函數:宏函數用 define 定義,核心是替換,目的爲了簡化長的函數名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 普通函數
void _jni_define_func_read() {
printf("read\n");
}
void _jni_define_func_write() {
printf("write\n");
}
// 定義宏函數
#define jni(NAME) _jni_define_func_##NAME() ;
void main() {
// 宏函數
//jni(read); 可以簡化函數名稱
jni(write) ;
system("pause");
}

8. 數組

聲明方式:
type arrayName [ arraySize ];
如:double balance[10];
初始化:
double balance[3] = {1,4,6}; 或 double balance[] = {1,4,6}

  • 多維數組:C++ 支持多維數組。多維數組最簡單的形式是二維數組。
  • 指向數組的指針:可以通過指定不帶索引的數組名稱來生成一個指向數組中第一個元素的指針。

    1
    2
    3
    4
    5
    6
    double *p;
    double balance[10];
    p = balance;
    // balance[4]:
    // *(balance + 4) 使用 balance 作爲地址的數組值
    // *(p + 4) 使用指針的數組值
  • 傳遞數組給函數:您可以通過指定不帶索引的數組名稱來給函數傳遞一個指向數組的指針。
    C++ 不允許向函數傳遞一個完整的數組作爲參數,但是,您可以通過指定不帶索引的數組名來傳遞一個指向數組的指針。

    1
    2
    3
    4
    5
    6
    //形參是一個指針
    void myFunction(int *param) {}
    //形參是一個已知大小的數組
    void myFunction(int param[10]) {}
    //形式參數是一個未定義大小的數組
    void myFunction(int param[]) {}
  • 從函數返回數組:C++ 允許從函數返回數組。
    C++ 不允許返回一個完整的數組作爲函數的參數。可以通過指定不帶索引的數組名來返回一個指向數組的指針。

    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
    #include <iostream>
    #include <cstdlib>
    #include <ctime>
    using namespace std;
    int * getRandom( )
    {
    //C++ 不支持在函數外返回局部變量的地址,除非定義局部變量爲 static 變量
    static int r[10];
    // 設置種子
    srand( (unsigned)time( NULL ) );
    for (int i = 0; i < 10; ++i)
    {
    r[i] = rand();
    cout << r[i] << endl;
    }
    return r;
    }
    int main ()
    {
    // 一個指向整數的指針
    int *p;
    p = getRandom();
    for ( int i = 0; i < 10; i++ )
    {
    cout << "*(p + " << i << ") : ";
    cout << *(p + i) << endl;
    }
    return 0;
    }

9. 字符串

C 的字符串:實際上是使用 null 字符 ‘\0’ 終止的一維字符數組,字符串複製、拼接、長度、比較等都需要用函數,strcpy(s1, s2)、strcat(s1, s2)、strlen(s1)、strcmp(s1, s2)……

1
2
3
#include <cstring>
char str1[11] = "Hello";

C++的字符串引入了 string 類型,一般操作和 Java 一樣,函數頭引入#include <string>

10. 指針

指針是一個變量,其值爲另一個變量的地址,即,內存位置的直接地址。就像其他變量或常量一樣,您必須在使用指針存儲其他變量地址之前,對其進行聲明。指針變量聲明的一般形式爲:
type var-name;
type 是指針的基類型,它必須是一個有效的 C++ 數據類型
幾個操作:*定義一個指針變量、把變量地址賦值給指針、訪問指針變量中可用地址的值

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
#include <iostream>
using namespace std;
int main ()
{
int var = 20; // 實際變量的聲明
int *ip; // 指針變量的聲明
ip = &var; // 1. 在指針變量中存儲 var 的地址
cout << "Value of var variable: ";
cout << var << endl;
// 2. 輸出在指針變量中存儲的地址
cout << "Address stored in ip variable: ";
cout << ip << endl;
// 3. 訪問指針中地址的值
cout << "Value of *ip variable: ";
cout << *ip << endl;
return 0;
}
/*
Value of var variable: 20
Address stored in ip variable: 0xbfc601ac
Value of *ip variable: 20
*/

1)Null 指針
在變量聲明的時候,如果沒有確切的地址可以賦值,爲指針變量賦一個 NULL 值是一個良好的編程習慣。賦爲 NULL 值的指針被稱爲空指針。
判空:

1
2
if(ptr) /* 如果 ptr 非空,則完成 */
if(!ptr) /* 如果 ptr 爲空,則完成 */

2)二級指針
就是指針的指針。記住多級指針裏面存儲的是上級指針的地址即可 。

1
2
3
4
5
int i = 10;
// p指針變量存儲的是i的內存地址
int* p = &i;
// p1指針變量存儲的是p的內存地址
int** p1 = &p;

對應的內存模擬圖
內存模擬圖

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
int add(int num1, int num2) {
return num1 + num2;
}
int minus(int num1, int num2) {
return num1 - num2;
}
// 將函數指針直接定義到函數形參中 , 類似java中的多態
// 我們可以函數指針作爲函數參數傳入
void showMsg(int(*c)(int num1, int num2), int a, int b) {
int r = c(a, b);
printf("計算完成=%d\n", r);
}
void main() {
showMsg(add, 10, 10);
showMsg(minus, 30, 2);
}
getchar();
/* 輸入:
計算完成=20
計算完成=28
*/

4)動態內存分配
在C語言中 , 我們在堆區開闢一塊空間使用的關鍵字是malloc , malloc函數定義:

1
2
3
4
5
6
7
void* __cdecl malloc(
_In_ _CRT_GUARDOVERFLOW size_t _Size
);
// 動態內存分配 , 使用malloc函數在對內存中開闢連續的內存空間 , 單位是:字節
// 申請一塊40M的堆內存
int* p = (int*)malloc(1024 *1024 * 10 * sizeof(int));

使用malloc申請內存 , 最重要的一個點就是可以動態改變申請的內存大小 , 可以使用realloc函數來重新申請內存大小,realloc函數定義:

1
2
3
4
5
6
7
void* __cdecl realloc(
_Pre_maybenull_ _Post_invalid_ void* _Block,
_In_ _CRT_GUARDOVERFLOW size_t _Size
);
// 重新申請內存大小 , 傳入申請的內存指針 , 申請內存總大小
int* p2 = realloc(p, (len + add) * sizeof(int));

內存分配的幾個注意細節:
(1)不能多次釋放
(2)釋放完之後 , 給指針置NULL,標誌釋放完成
(3)內存泄漏 (p重新賦值之後 , 再free , 並沒有真正釋放 , 要在賦值之前釋放前一個內存空間)

11. 引用

引用變量是一個別名,也就是說,它是某個已存在變量的另一個名字。
引用和指針的區別:
1)不存在空引用。引用必須連接到一塊合法的內存。
2)一旦引用被初始化爲一個對象,就不能被指向到另一個對象。指針可以在任何時候指向到另一個對象。
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
#include <iostream>
using namespace std;
int main ()
{
// 聲明簡單的變量
int i;
double d;
// 聲明引用變量
int& r = i; //r 是一個初始化爲 i 的整型引用
double& s = d; //s 是一個初始化爲 d 的 double 型引用
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}

a. 把引用作爲參數,如交換兩個數

1
2
3
4
5
6
7
8
9
void swap(int& x, int& y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 賦值給 x */
y = temp; /* 把 x 賦值給 y */
return;
}

b. 把引用作爲返回值

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
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues( int i )
{
return vals[i]; // 返回第 i 個元素的引用
}
// 要調用上面定義函數的主函數
int main ()
{
cout << "改變前的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
setValues(1) = 20.23; // 改變第 2 個元素
setValues(3) = 70.8; // 改變第 4 個元素
cout << "改變後的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
return 0;
}
/*
改變前的值
vals[0] = 10.1
vals[1] = 12.6
vals[2] = 33.1
vals[3] = 24.1
vals[4] = 50
改變後的值
vals[0] = 10.1
vals[1] = 20.23
vals[2] = 33.1
vals[3] = 70.8
vals[4] = 50
*/

當返回一個引用時,要注意被引用的對象不能超出作用域。所以返回一個對局部變量的引用是不合法的,但是,可以返回一個對靜態變量的引用。

1
2
3
4
5
6
int& func() {
int q;
//! return q; // 在編譯時發生錯誤
static int x;
return x; // 安全,x 在函數作用域外依然是有效的
}

12. 時間和日期

有四個與時間相關的類型:clock_t、time_t、size_t 和 tm。類型 clock_t、size_t 和 time_t 能夠把系統時間和日期表示爲某種整數。頭文件:#include <ctime>C++ 日期 & 時間 | 菜鳥教程
結構類型 tm 把日期和時間以 C 結構的形式保存,tm 結構的定義如下:

1
2
3
4
5
6
7
8
9
10
11
struct tm {
int tm_sec; // 秒,正常範圍從 0 到 59,但允許至 61
int tm_min; // 分,範圍從 0 到 59
int tm_hour; // 小時,範圍從 0 到 23
int tm_mday; // 一月中的第幾天,範圍從 1 到 31
int tm_mon; // 月,範圍從 0 到 11
int tm_year; // 自 1900 年起的年數
int tm_wday; // 一週中的第幾天,範圍從 0 到 6,從星期日算起
int tm_yday; // 一年中的第幾天,範圍從 0 到 365,從 1 月 1 日算起
int tm_isdst; // 夏令時
}

13. 輸入輸出

該文件定義了 cin、cout、cerr 和 clog 對象,分別對應於標準輸入流、標準輸出流、非緩衝標準錯誤流和緩衝標準錯誤流。

14. 數據結構

struct 語句的格式如下:

1
2
3
4
5
6
7
struct [structure tag]
{
member definition;
member definition;
...
member definition;
} [one or more structure variables];

訪問結構的成員,使用成員訪問運算符(.)

1)結構體的兩種創建方式,①通過字面量的方式創建 。② 通過定義結構體變量然後給成員賦值 , 類似對象給成員屬性賦值。
2)字符數組的賦值,不能直接=”xxx”,而需要使用strcpy()函數 。

15. 類和對象

沒有修飾符,默認爲 private

使用初始化列表來初始化字段

1
2
3
4
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
//....
}

1)類的析構函數
類的析構函數是類的一種特殊的成員函數,它會在每次刪除所創建的對象時執行。

2)拷貝構造函數
一種特殊的構造函數,它在創建對象時,是使用同一類中之前創建的對象來初始化新創建的對象。拷貝構造函數通常用於:

- 通過使用另一個同類型的對象來初始化新創建的對象。
- 複製對象把它作爲參數傳遞給函數。
- 複製對象,並從函數返回這個對象

如果在類中沒有定義拷貝構造函數,編譯器會自行定義一個。如果類帶有指針變量,並有動態內存分配,則它必須有一個拷貝構造函數。拷貝構造函數的最常見形式如下:

1
2
3
classname (const classname &obj) {
// 構造函數的主體
}

3)友元函數
類的友元函數是定義在類外部,但有權訪問類的所有私有(private)成員和保護(protected)成員。儘管友元函數的原型有在類的定義中出現過,但是友元函數並不是成員函數。
友元可以是一個函數,該函數被稱爲友元函數;友元也可以是一個類,該類被稱爲友元類,在這種情況下,整個類及其所有成員都是友元。
如果要聲明函數爲一個類的友元,需要在類定義中該函數原型前使用關鍵字 friend

4)內聯函數
內聯函數是通常與類一起使用。如果一個函數是內聯的,那麼在編譯時,編譯器會把該函數的代碼副本放置在每個調用該函數的地方。

如果想把一個函數定義爲內聯函數,則需要在函數名前面放置關鍵字 inline,在調用函數之前需要對函數進行定義

5)this 指針
在 C++ 中,每一個對象都能通過 this 指針來訪問自己的地址。this 指針是所有成員函數的隱含參數。因此,在成員函數內部,它可以用來指向調用對象。
友元函數沒有 this 指針,因爲友元不是類的成員。只有成員函數纔有 this 指針。
this->method()

6)類的靜態成員
聲明類的成員爲靜態時,這意味着無論創建多少個類的對象,靜態成員都只有一個副本。
靜態成員函數即使在類對象不存在的情況下也能被調用,靜態函數只要使用類名加範圍解析運算符 :: 就可以訪問。
靜態成員函數只能訪問靜態數據成員,不能訪問其他靜態成員函數和類外部的其他函數。

16. 繼承

一個類可以派生自多個類,這意味着,它可以從多個基類繼承數據和函數。定義一個派生類,我們使用一個類派生列表來指定基類。類派生列表以一個或多個基類命名,形式如下:
class derived-class: access-specifier base-class
其中,訪問修飾符 access-specifier 是 public、protected 或 private 其中的一個,base-class 是之前定義過的某個類的名稱。如果未使用訪問修飾符 access-specifier,則默認爲 private。

不同於 Java,C++支持多重繼承。

未完待續……> 前言:下文是很基礎的C++語法的 notes,只是個人學習 C++時,針對自己不熟悉的知識點做的零散的筆記,方便後期閱讀。對於很熟悉 C++的人來說,不具備閱讀價值。

學習教程:C++ 教程 | 菜鳥教程

1. 局部變量和全局變量初始化問題

當局部變量被定義時,系統不會對其初始化,您必須自行對其初始化。定義全局變量時,系統會自動初始化爲下列值,int - 0, char- ‘\0’,float - 0 等

2. 定義常量方式

C++中有兩種方式, C++ 常量 | 菜鳥教程

  • 使用宏定義#define預處理器(不需要分號,不需要聲明類型
    形式:#define identifier value,如 # define LENGTH 10
  • 使用 const 關鍵字
    形式:const type variable = value;
    常量名定義和 Java 一樣,最好使用大寫字母形式,同時注意常量定義必須有值,也不能被重新賦值(不然就不叫常量了)

兩種定義方式的區別:

  • 類型和安全檢查不同
    宏定義是字符替換,沒有數據類型的區別,同時這種替換沒有類型安全檢查,可能產生邊際效應等錯誤;const 常量是常量的聲明,有類型區別,需要在編譯階段進行類型檢查
  • 編譯器處理不同
    宏定義是一個”編譯時”概念,在預處理階段展開,不能對宏定義進行調試,生命週期結束與編譯時期;const常量是一個”運行時”概念,在程序運行使用,類似於一個只讀行數據
  • 存儲方式不同
    宏定義是直接替換,不會分配內存,存儲與程序的代碼段中;const常量需要進行內存
  • 定義域不同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void f1 ()
    {
    #define N 12
    const int n 12;
    }
    void f2 ()
    {
    cout<<N <<endl; //正確,N已經定義過,不受定義域限制
    cout<<n <<endl; //錯誤,n定義域只在f1函數中
    }
  • 定義後能否取消
    宏定義可以通過#undef來使之前的宏定義失效;const 常量定義後將在定義域內永久有效

    1
    2
    3
    4
    5
    6
    7
    8
    void f1()
    {
    #define N 12
    const int n = 12;
    #undef N //取消宏定義後,即使在f1函數中,N也無效了
    #define N 21//取消後可以重新定義
    }
  • 是否可以做函數參數
    宏定義不能作爲參數傳遞給函數
    const常量可以在函數的參數列表中出現

另外,還有 typedef 的關鍵字,用來對一個類型取一個新名字,目的是爲了使源代碼更易於閱讀和理解。如在 JNI 的頭部文件會看到這種

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifdef HAVE_INTTYPES_H
# include <inttypes.h> /* C99 */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#else
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#endif

3. C++修飾符類型

數據類型修飾符:signed, unsigned, long, short

  • 修飾整形:signed, unsigned, long, short
  • 修飾字符型:signed, unsigned
  • 修飾雙精度性:long
  • long 或 short 的修飾符前綴:signed, unsigned
    C++ 速記符號聲明無符號短整數或無符號長整數,可以使用 unsigned、short 或 unsigned、long,其中 int 是隱含的

類型限定符
const:對象在程序執行期間不能被修改改變。
volatile:修飾符 volatile 告訴編譯器,變量的值可能以程序未明確指定的方式被改變。
restrict:由 restrict 修飾的指針是唯一一種訪問它所指向的對象的方式。只有 C99 增加了新的類型限定符 restrict。

4. 存儲類

1)static 存儲類
static 存儲類指示編譯器在程序的生命週期內保持局部變量的存在,而不需要在每次它進入和離開作用域時進行創建和銷燬。因此,使用 static 修飾局部變量可以在函數調用之間保持局部變量的值。static 修飾符也可以應用於全局變量。當 static 修飾全局變量時,會使變量的作用域限制在聲明它的文件內。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
// 函數聲明
void func(void);
static int count = 4; /* 全局變量 */
int main()
{
while(count--)
{
func();
}
return 0;
}
// 函數定義
void func( void )
{
static int i = 5; // 局部靜態變量
i++;
std::cout << "變量 i 爲 " << i ;
std::cout << " , 變量 count 爲 " << count << std::endl;
}

輸出爲:

1
2
3
4
變量 i 爲 6 , 變量 count 爲 3
變量 i 爲 7 , 變量 count 爲 2
變量 i 爲 8 , 變量 count 爲 1
變量 i 爲 9 , 變量 count 爲 0

2)extern 存儲類
extern 存儲類用於提供一個全局變量的引用,全局變量對所有的程序文件都是可見的。當您使用 ‘extern’ 時,對於無法初始化的變量,會把變量名指向一個之前定義過的存儲位置。
當您有多個文件且定義了一個可以在其他文件中使用的全局變量或函數時,可以在其他文件中使用 extern 來得到已定義的變量或函數的引用。可以這麼理解,extern 是用來在另一個文件中聲明一個全局變量或函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//main.cpp
#include <iostream>
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
//support.cpp
#include <iostream>
extern int count;//聲明已經在main.cpp 中定義的 count
void write_extern(void)
{
std::cout << "Count is " << count << std::endl;
}

結果爲:Count is 5

3)mutable存儲類
mutable 說明符僅適用於類的對象。它允許對象的成員替代常量。也就是說,mutable 成員可以通過 const 成員函數修改。

4)thread_local 存儲類
使用 thread_local 說明符聲明的變量僅可在它在其上創建的線程上訪問。 變量在創建線程時創建,並在銷燬線程時銷燬。 每個線程都有其自己的變量副本。
thread_local 說明符可以與 static 或 extern 合併。
可以將 thread_local 僅應用於數據聲明和定義,thread_local 不能用於函數聲明或定義。

5. 運算符

a. 位運算符
位運算符有點忘記了,做下筆記記錄下

1)按位或(OR):p | q
兩個數的相應二進制位,只要有1,則爲1
2)按位與(AND):p & q
兩個數的相應二進制位,只有同爲1,才爲1
3)按位異或(XOR):p ^ q
兩個數的相應二進制位,同則爲0,不同爲1
4)取反(NOT):~p
二進制位,1變爲0 ,0變爲1
5)移位
左移:<< ,右移:>>

b. 雜項運算符
1)sizeof——sizeof 運算符返回變量的大小。例如,sizeof(a) 將返回 4,其中 a 是整數。
2)Condition ? X : Y——條件運算符。如果 Condition 爲真 ? 則值爲 X : 否則值爲 Y。
3), ——逗號運算符會順序執行一系列運算。整個逗號表達式的值是以逗號分隔的列表中的最後一個表達式的值。
4).(點)和 ->(箭頭)—— 成員運算符用於引用類、結構和共用體的成員。
5)Cast —— 強制轉換運算符把一種數據類型轉換爲另一種數據類型。例如,int(2.2000) 將返回 2。
6)& —— 指針運算符 & 返回變量的地址。例如 &a; 將給出變量的實際地址。
7) —— 指針運算符 指向一個變量。例如,*var; 將指向變量 var。

6. 循環

C++有 goto 語句;
C++ 程序員偏向於使用 for(;;) 結構來表示一個無限循環;
Switch 語句裏面的判斷類型,必須是一個整型或枚舉類型,或者是一個有單一轉換函數將其轉換爲整型或枚舉類型的 class 類型;

7. 函數

C++需要在調用函數頂部聲明函數,Java 不需要;

函數參數:

如果函數要使用參數,則必須聲明接受參數值的變量。這些變量稱爲函數的形式參數。形式參數就像函數內的其他局部變量,在進入函數時被創建,退出函數時被銷燬。

當調用函數時,有兩種向函數傳遞參數的方式:
1)傳值調用——該方法把參數的實際值複製給函數的形式參數。在這種情況下,修改函數內的形式參數對實際參數沒有影響。
2)指針調用——該方法把參數的地址複製給形式參數。在函數內,該地址用於訪問調用中要用到的實際參數。這意味着,修改形式參數會影響實際參數。
3)引用調用——該方法把參數的引用複製給形式參數。在函數內,該引用用於訪問調用中要用到的實際參數。這意味着,修改形式參數會影響實際參數。

默認情況下,C++ 使用傳值調用來傳遞參數。一般來說,這意味着函數內的代碼不能改變用於調用函數的參數。

Lambda函數與表達式
表達式形式:[capture](parameters)->return-type{body}

注意:
宏函數:宏函數用 define 定義,核心是替換,目的爲了簡化長的函數名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 普通函數
void _jni_define_func_read() {
printf("read\n");
}
void _jni_define_func_write() {
printf("write\n");
}
// 定義宏函數
#define jni(NAME) _jni_define_func_##NAME() ;
void main() {
// 宏函數
//jni(read); 可以簡化函數名稱
jni(write) ;
system("pause");
}

8. 數組

聲明方式:
type arrayName [ arraySize ];
如:double balance[10];
初始化:
double balance[3] = {1,4,6}; 或 double balance[] = {1,4,6}

  • 多維數組:C++ 支持多維數組。多維數組最簡單的形式是二維數組。
  • 指向數組的指針:可以通過指定不帶索引的數組名稱來生成一個指向數組中第一個元素的指針。

    1
    2
    3
    4
    5
    6
    double *p;
    double balance[10];
    p = balance;
    // balance[4]:
    // *(balance + 4) 使用 balance 作爲地址的數組值
    // *(p + 4) 使用指針的數組值
  • 傳遞數組給函數:您可以通過指定不帶索引的數組名稱來給函數傳遞一個指向數組的指針。
    C++ 不允許向函數傳遞一個完整的數組作爲參數,但是,您可以通過指定不帶索引的數組名來傳遞一個指向數組的指針。

    1
    2
    3
    4
    5
    6
    //形參是一個指針
    void myFunction(int *param) {}
    //形參是一個已知大小的數組
    void myFunction(int param[10]) {}
    //形式參數是一個未定義大小的數組
    void myFunction(int param[]) {}
  • 從函數返回數組:C++ 允許從函數返回數組。
    C++ 不允許返回一個完整的數組作爲函數的參數。可以通過指定不帶索引的數組名來返回一個指向數組的指針。

    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
    #include <iostream>
    #include <cstdlib>
    #include <ctime>
    using namespace std;
    int * getRandom( )
    {
    //C++ 不支持在函數外返回局部變量的地址,除非定義局部變量爲 static 變量
    static int r[10];
    // 設置種子
    srand( (unsigned)time( NULL ) );
    for (int i = 0; i < 10; ++i)
    {
    r[i] = rand();
    cout << r[i] << endl;
    }
    return r;
    }
    int main ()
    {
    // 一個指向整數的指針
    int *p;
    p = getRandom();
    for ( int i = 0; i < 10; i++ )
    {
    cout << "*(p + " << i << ") : ";
    cout << *(p + i) << endl;
    }
    return 0;
    }

9. 字符串

C 的字符串:實際上是使用 null 字符 ‘\0’ 終止的一維字符數組,字符串複製、拼接、長度、比較等都需要用函數,strcpy(s1, s2)、strcat(s1, s2)、strlen(s1)、strcmp(s1, s2)……

1
2
3
#include <cstring>
char str1[11] = "Hello";

C++的字符串引入了 string 類型,一般操作和 Java 一樣,函數頭引入#include <string>

10. 指針

指針是一個變量,其值爲另一個變量的地址,即,內存位置的直接地址。就像其他變量或常量一樣,您必須在使用指針存儲其他變量地址之前,對其進行聲明。指針變量聲明的一般形式爲:
type var-name;
type 是指針的基類型,它必須是一個有效的 C++ 數據類型
幾個操作:*定義一個指針變量、把變量地址賦值給指針、訪問指針變量中可用地址的值

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
#include <iostream>
using namespace std;
int main ()
{
int var = 20; // 實際變量的聲明
int *ip; // 指針變量的聲明
ip = &var; // 1. 在指針變量中存儲 var 的地址
cout << "Value of var variable: ";
cout << var << endl;
// 2. 輸出在指針變量中存儲的地址
cout << "Address stored in ip variable: ";
cout << ip << endl;
// 3. 訪問指針中地址的值
cout << "Value of *ip variable: ";
cout << *ip << endl;
return 0;
}
/*
Value of var variable: 20
Address stored in ip variable: 0xbfc601ac
Value of *ip variable: 20
*/

1)Null 指針
在變量聲明的時候,如果沒有確切的地址可以賦值,爲指針變量賦一個 NULL 值是一個良好的編程習慣。賦爲 NULL 值的指針被稱爲空指針。
判空:

1
2
if(ptr) /* 如果 ptr 非空,則完成 */
if(!ptr) /* 如果 ptr 爲空,則完成 */

2)二級指針
就是指針的指針。記住多級指針裏面存儲的是上級指針的地址即可 。

1
2
3
4
5
int i = 10;
// p指針變量存儲的是i的內存地址
int* p = &i;
// p1指針變量存儲的是p的內存地址
int** p1 = &p;

對應的內存模擬圖
[image:7FE1BA81-E50D-4AEA-A921-A5B5856A21B8-9472-000031D6922E1E79/40E1C3D8-4664-4D93-89F0-49B1BCD0433F.png]

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
int add(int num1, int num2) {
return num1 + num2;
}
int minus(int num1, int num2) {
return num1 - num2;
}
// 將函數指針直接定義到函數形參中 , 類似java中的多態
// 我們可以函數指針作爲函數參數傳入
void showMsg(int(*c)(int num1, int num2), int a, int b) {
int r = c(a, b);
printf("計算完成=%d\n", r);
}
void main() {
showMsg(add, 10, 10);
showMsg(minus, 30, 2);
}
getchar();
/* 輸入:
計算完成=20
計算完成=28
*/

4)動態內存分配
在C語言中 , 我們在堆區開闢一塊空間使用的關鍵字是malloc , malloc函數定義:

1
2
3
4
5
6
7
void* __cdecl malloc(
_In_ _CRT_GUARDOVERFLOW size_t _Size
);
// 動態內存分配 , 使用malloc函數在對內存中開闢連續的內存空間 , 單位是:字節
// 申請一塊40M的堆內存
int* p = (int*)malloc(1024 *1024 * 10 * sizeof(int));

使用malloc申請內存 , 最重要的一個點就是可以動態改變申請的內存大小 , 可以使用realloc函數來重新申請內存大小,realloc函數定義:

1
2
3
4
5
6
7
void* __cdecl realloc(
_Pre_maybenull_ _Post_invalid_ void* _Block,
_In_ _CRT_GUARDOVERFLOW size_t _Size
);
// 重新申請內存大小 , 傳入申請的內存指針 , 申請內存總大小
int* p2 = realloc(p, (len + add) * sizeof(int));

內存分配的幾個注意細節:
(1)不能多次釋放
(2)釋放完之後 , 給指針置NULL,標誌釋放完成
(3)內存泄漏 (p重新賦值之後 , 再free , 並沒有真正釋放 , 要在賦值之前釋放前一個內存空間)

11. 引用

引用變量是一個別名,也就是說,它是某個已存在變量的另一個名字。
引用和指針的區別:
1)不存在空引用。引用必須連接到一塊合法的內存。
2)一旦引用被初始化爲一個對象,就不能被指向到另一個對象。指針可以在任何時候指向到另一個對象。
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
#include <iostream>
using namespace std;
int main ()
{
// 聲明簡單的變量
int i;
double d;
// 聲明引用變量
int& r = i; //r 是一個初始化爲 i 的整型引用
double& s = d; //s 是一個初始化爲 d 的 double 型引用
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}

a. 把引用作爲參數,如交換兩個數

1
2
3
4
5
6
7
8
9
void swap(int& x, int& y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 賦值給 x */
y = temp; /* 把 x 賦值給 y */
return;
}

b. 把引用作爲返回值

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
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues( int i )
{
return vals[i]; // 返回第 i 個元素的引用
}
// 要調用上面定義函數的主函數
int main ()
{
cout << "改變前的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
setValues(1) = 20.23; // 改變第 2 個元素
setValues(3) = 70.8; // 改變第 4 個元素
cout << "改變後的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
return 0;
}
/*
改變前的值
vals[0] = 10.1
vals[1] = 12.6
vals[2] = 33.1
vals[3] = 24.1
vals[4] = 50
改變後的值
vals[0] = 10.1
vals[1] = 20.23
vals[2] = 33.1
vals[3] = 70.8
vals[4] = 50
*/

當返回一個引用時,要注意被引用的對象不能超出作用域。所以返回一個對局部變量的引用是不合法的,但是,可以返回一個對靜態變量的引用。

1
2
3
4
5
6
int& func() {
int q;
//! return q; // 在編譯時發生錯誤
static int x;
return x; // 安全,x 在函數作用域外依然是有效的
}

12. 時間和日期

有四個與時間相關的類型:clock_t、time_t、size_t 和 tm。類型 clock_t、size_t 和 time_t 能夠把系統時間和日期表示爲某種整數。頭文件:#include <ctime>C++ 日期 & 時間 | 菜鳥教程
結構類型 tm 把日期和時間以 C 結構的形式保存,tm 結構的定義如下:

1
2
3
4
5
6
7
8
9
10
11
struct tm {
int tm_sec; // 秒,正常範圍從 0 到 59,但允許至 61
int tm_min; // 分,範圍從 0 到 59
int tm_hour; // 小時,範圍從 0 到 23
int tm_mday; // 一月中的第幾天,範圍從 1 到 31
int tm_mon; // 月,範圍從 0 到 11
int tm_year; // 自 1900 年起的年數
int tm_wday; // 一週中的第幾天,範圍從 0 到 6,從星期日算起
int tm_yday; // 一年中的第幾天,範圍從 0 到 365,從 1 月 1 日算起
int tm_isdst; // 夏令時
}

13. 輸入輸出

該文件定義了 cin、cout、cerr 和 clog 對象,分別對應於標準輸入流、標準輸出流、非緩衝標準錯誤流和緩衝標準錯誤流。

14. 數據結構

struct 語句的格式如下:

1
2
3
4
5
6
7
struct [structure tag]
{
member definition;
member definition;
...
member definition;
} [one or more structure variables];

訪問結構的成員,使用成員訪問運算符(.)

1)結構體的兩種創建方式,①通過字面量的方式創建 。② 通過定義結構體變量然後給成員賦值 , 類似對象給成員屬性賦值。
2)字符數組的賦值,不能直接=”xxx”,而需要使用strcpy()函數 。

15. 類和對象

沒有修飾符,默認爲 private

使用初始化列表來初始化字段

1
2
3
4
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
//....
}

1)類的析構函數
類的析構函數是類的一種特殊的成員函數,它會在每次刪除所創建的對象時執行。

2)拷貝構造函數
一種特殊的構造函數,它在創建對象時,是使用同一類中之前創建的對象來初始化新創建的對象。拷貝構造函數通常用於:

- 通過使用另一個同類型的對象來初始化新創建的對象。
- 複製對象把它作爲參數傳遞給函數。
- 複製對象,並從函數返回這個對象

如果在類中沒有定義拷貝構造函數,編譯器會自行定義一個。如果類帶有指針變量,並有動態內存分配,則它必須有一個拷貝構造函數。拷貝構造函數的最常見形式如下:

1
2
3
classname (const classname &obj) {
// 構造函數的主體
}

3)友元函數
類的友元函數是定義在類外部,但有權訪問類的所有私有(private)成員和保護(protected)成員。儘管友元函數的原型有在類的定義中出現過,但是友元函數並不是成員函數。
友元可以是一個函數,該函數被稱爲友元函數;友元也可以是一個類,該類被稱爲友元類,在這種情況下,整個類及其所有成員都是友元。
如果要聲明函數爲一個類的友元,需要在類定義中該函數原型前使用關鍵字 friend

4)內聯函數
內聯函數是通常與類一起使用。如果一個函數是內聯的,那麼在編譯時,編譯器會把該函數的代碼副本放置在每個調用該函數的地方。

如果想把一個函數定義爲內聯函數,則需要在函數名前面放置關鍵字 inline,在調用函數之前需要對函數進行定義

5)this 指針
在 C++ 中,每一個對象都能通過 this 指針來訪問自己的地址。this 指針是所有成員函數的隱含參數。因此,在成員函數內部,它可以用來指向調用對象。
友元函數沒有 this 指針,因爲友元不是類的成員。只有成員函數纔有 this 指針。
this->method()

6)類的靜態成員
聲明類的成員爲靜態時,這意味着無論創建多少個類的對象,靜態成員都只有一個副本。
靜態成員函數即使在類對象不存在的情況下也能被調用,靜態函數只要使用類名加範圍解析運算符 :: 就可以訪問。
靜態成員函數只能訪問靜態數據成員,不能訪問其他靜態成員函數和類外部的其他函數。

16. 繼承

一個類可以派生自多個類,這意味着,它可以從多個基類繼承數據和函數。定義一個派生類,我們使用一個類派生列表來指定基類。類派生列表以一個或多個基類命名,形式如下:
class derived-class: access-specifier base-class
其中,訪問修飾符 access-specifier 是 public、protected 或 private 其中的一個,base-class 是之前定義過的某個類的名稱。如果未使用訪問修飾符 access-specifier,則默認爲 private。

不同於 Java,C++支持多重繼承。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章