C++筆記(一)
反復考量之后,還是決定將C++作為我的第二語言以及以后的主力開發語言。
語法基礎
基本數據類型
基本有四種類型:
- 整型(修飾符:short、long、signed、unsigned)
- 浮點型(包括float和double,修飾符:long)
- 字符型
- 布爾型
注意數字類型存在有無符號的區別,有無符號的計算參考下面的[補碼](# 補碼)
變量、常量
#include <iostream>
using namespace std;
int main()
{
// 變量
string color; // 聲明變量,標識符基本是通用約束:字母數字下劃線,不能以下劃線開頭,不能使用關鍵字,大小寫敏感
int age; // 其他還有16位short int和32位long int等
double weight; // 還有float和long double等
char c; // 以上多數類型包含有符號和無符號類型
bool b;
color = "三花"; // 首次賦值,初始化變量,變量需要初始化才可以引用,沒有其它語言中的默認零值
age = 3;
weight = 4.0;
c = 'a';
b = false;
cout << color << age << weight << c << b << endl;
// 常量
// 整數前綴進制基數0x表示十六進制,0表示八進制,不帶前綴默認十進制
// 整數后綴可以是U或者L,U表示無符號,L表示長整數
const double PI = 3.1415926;
cout << PI;
}
作用域
#include <assert.h>
int get_var();
int get_static_var();
int x = 1;
int main()
{
int a = get_var();
int b = get_static_var();
assert(a == 1 && b == 2);
a = get_var();
b = get_static_var();
assert(a == 1 && b == 3);
int x = 0;
assert(::x == 1); // 通過::調用被shadow掉的全局變量
}
int get_var()
{
int a = 0;
a++;
return a;
}
int get_static_var()
{
// 靜態局部變量只會初始化一次,生命周期和程序相同
// 全局變量和靜態局部變量可以自動初始化,局部變量不會自動初始化
static int b = 1;
b++;
return b;
}
基本運算
#include <assert.h>
int main()
{
int x = 5, y = 2;
// 算數運算符
assert(x / y == 2); // 地板除
assert(5.0 / y == 2.5);
assert(++x == 6); // 當自增/自減符前置時,變量先于當前語句的執行進行自增自減操作并更新值
assert(x-- == 6); // 當自增自減符后置時,變量會在當前語句執行后再進行自增自減操作并更新值
// 關系運算符
// 等于==;不等于!=;大于>;小于<;大于等于>=;小于等于<=
assert(x != y);
// 邏輯運算符
// 與&&;或||;非!
bool t = true, f = false;
assert(t || f);
// 賦值運算符
// 賦值=;加賦+=;減賦-=;乘賦*=;除賦/=;模賦%=;左移后賦值<<=;右移后賦值>>=;按位與后賦值&=;按位異或后賦值^=;按位或后賦值|=
f += 1;
assert(f);
y <<= 1;
assert(y == 4);
// 位運算符
// 按位與&:僅當兩個操作數都為1時為1;按位或:僅當兩個操作數都為0時為0;按位異或:僅當兩個操作數不同時為1;按位取反~
int z = 10;
assert(~z == -11);
// 其他運算符
// 三目運算符:條件語句?真時值:假時值;逗號運算符:順序執行多個運算,值為最后表達式的值;...
assert(true?1:2 == 1);
assert(sizeof(x) == 4);
assert((x, y, z) == 10);
}
#include <assert.h>
int main()
{
// c++也有條件判斷短路
int x = 0;
if (x && ++x) {}
assert(x == 0);
int y = 1;
if (y || ++y) {}
assert(y == 1);
}
#include <iostream>
using namespace std;
int main()
{
float a = 0.1;
float b = 0.0;
cout << a / b << endl; // inf
float c = 0.0;
float d = 0.0;
cout << c / d << endl; // -nan(ind)
}
補碼
無符號數表示:按位加權求和即可
有符號數表示:計算機中統一使用補碼,補碼可以統一符號位和數值域、加減法運算,對于計算機來說,對于不同位(符號位、數值域)上的值的無差別處理可以簡化底層硬件設計和實現。
通過補碼可以構造出一張映射表,這樣減法就可以通過:
減數加上被減數的映射值x得出y,通過映射表得到y的映射值z,z即減法結果
#include <assert.h>
unsigned int B2U(unsigned int num)
{
return (unsigned int)(num);
}
int B2T(int num)
{
return (int)(num);
}
int main()
{
assert(B2U(0xFFFFFFFF) == 4294967295);
assert(B2T(0xFFFFFFFF) == -1);
assert(B2T(0x80000000) == -2147483648);
}
對于有符號數不要使用右移
反碼:區分正數和負數,正數正常表示即可,負數保留符號位,將數值域按位取反,然后加1
反碼更接近人類思考方式,但不能用。很多時候,如果有多種方案作為計算機運作時的解決方案,首先應該排除更接近人類常規思維方式的方案,因為這種方案很可能不適合計算機發揮性能。
字節序
一個字(32位機器的4個byte或32個bit)以byte存放的方式
大端序:IBM大型機或者網絡傳輸,高位在前,整體和字節內部都有序
小端序:intel兼容機,高位在后,每個字節內部有序
基本結構
順序結構
略
分支結構
#include <iostream>
using namespace std;
int main()
{
cout << "請輸入x:" << endl;
string x;
cin >> x;
if (x == "if")
{
if (1)
{
cout << "if分支" << endl;
}
}
// else if實際上是嵌套的if語句
else if (x == "else-if")
{
cout << "else if分支" << endl;
}
else
{
cout << x << endl;
cout << "else分支" << endl;
}cout << "請輸入y:" << endl;
int y;
cin >> y;
switch (y)
{
case 1:
cout << 3 << endl;
break; // 默認是不跳出的
case 2:
cout << 4 << endl;
break;
default:
cout << "default" << endl;
}
}
循環結構
#include <assert.h>
int main()
{
int x = 0;
while (x < 10) {
if (x == 6)
{
x += 2;
continue;
}
else if (x == 8)
{
break;
}
x++;
}
assert(x == 8);
for (int y = 0; y < 10; y++)
{
if (x == 6)
{
x += 2;
continue;
}
else if (x == 8)
{
break;
}
}
assert(x == 8);
// 支持do...while...
// 支持goto
++x;
goto here;
++x;
++x;
++x;
here:
--x;
assert(x == 8);
}
指針
指針是基于基本數據類型的復合數據類型,占8個字節。
#include <iostream>
#include <assert.h>
using namespace std;
void print_p(void* p);
int main()
{
int n = 1;
int* p = &n;
n = 2;
assert(*p == 2);
int a = 1;
const int* pa = &a;
a = 2;
// *pa = 3; // 常量指針,不能通過指針直接修改變量的值,可以重新指向其他變量(也不可通過指針修改值)
assert(*pa == 2);
int b = 1;
int* const pb = &b;
b = 2;
// pb = &a; // 指針常量,可以通過指針修改變量的值,不可以重新指向其他變量
assert(*pb == 2);
// 另外有常指針常量,不可通過指針修改變量的值,也不可重新指向其他變量
print_p(&b);
}
void print_p(void* p)
{
cout << p << "指向的值是:" << *((int*) p) << endl;
}
內存空間
- 內核空間
- 用戶空間
- 棧:局部變量、函數參數和返回值(降序分配內存地址)
- 堆:動態開辟內存的變量(升序分配內存地址)
- 數據段:全局變量、靜態變量
- 代碼段:可執行代碼、常量(程序開始運行后不變)
動態分配內存
主要是為了使用更大空間的堆區內存和手動控制內存釋放
#include <assert.h>
void settle_mem(int** pp)
{
*pp = new int(6);
}
int main()
{
int* n = new int{ 5 };
*n += 5;
assert(*n == 10);
delete n;
}
二級指針
#include <assert.h>
void settle_mem(int** pp)
{
*pp = new int(6);
}
int main()
{
// 二級指針
int x = 6;
int* px = &x;
int** ppx = &px;
assert(*ppx == &x);
assert(**ppx == x);
// 可以通過二級指針為指針分配內存
int* p;
settle_mem(&p);
assert(*p == 6);
}
空指針
可以用0或者NULL表示空指針,可以用來屏蔽編譯錯誤。
解引用空指針會引起程序崩潰,delete空指針會被忽略。
無論一個指向0、NULL、nullptr,都可以用三者中的任意一個值與指針比較來確認是否是空指針。
#include <assert.h>
int main()
{
// 空指針
int* p = NULL;
assert(p == 0);
assert(p == NULL);
assert(p == nullptr);
}
野指針
主要是指沒有初始化的、動態分配的內存已被釋放的、自動分配的內存已被回收的或者數組越界的指針,訪問野指針可能導致程序崩潰。至于解決辦法,就是針對可能出現野指針的情形進行規避,不出現野指針就解決了野指針的問題。
函數指針
#include <iostream>
using namespace std;
int my_add(int a, int b);
int calculate(int (*f)(int, int), int a, int b);
int main()
{
// 函數指針
calculate(my_add, 1, 2);
}
int my_add(int a, int b)
{
return a + b;
}
int calculate(int (*f)(int, int), int a, int b)
{
// 只是演示,實際上這里可以直接訪問到全局標識符my_add
int res = f(a, b);
cout << "a=" << a << ", b=" << b << ", res=" << res << endl;
return res;
}
常見容器類型
數組
數組要求數據類型相同。
數組空間在內存中是連續的,數組名多數時候被認為是第0個元素的地址(少數語句中代表其他含義,例如sizeof運算符作用在數組名時將返回數組的字節數),通過數組地址+n可以訪問到下標為n的數組元素,數組名是常量,不可修改。
當編譯器遇到地址[下標]時會認為是**(地址與下標之和)*
對數組取址將會得到行指針(值與數組第0個元素地址相同),行指針+1將得到下一個行指針即,第0個元素地址+數組字節數。
#include <iostream>
#include <assert.h>
using namespace std;
void print_2d_arr(int rp[][6], int rows);
void print_arr(int arr[], int len);
int comp_asc(const void* p1, const void* p2);
int comp_desc(const void* p1, const void* p2);
int bin_search(int arr[], int len, int target);
int main()
{
// int arr[get_len()]; 表達式必須在編譯器可被計算
// int num = 6; int arr[num];表達式應包含常量
const int num = 6; int arr[num];
for (int i = 0; i < 6; i++)
{
arr[i] = i * i;
}
assert(arr[5] == 25);
// 數組支持使用{}聲明時直接初始化賦值,并且支持長度推導,支持手動初始化時自動零值
// 數組拷貝
int arr1[num];
memcpy(arr1, arr, sizeof(arr));
// 數組清零
memset(arr, 0, sizeof(arr));
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
assert(arr[i] == 0);
}
// 當編譯器遇到 地址[下標] 時會認為是 *(地址與下標之和)
int* p = arr1;
for (int i = 0; i < sizeof(arr1) / sizeof(int); i++)
{
assert(arr1[i] == *(p+i));
}
// 數組排序
int to_sort[6] = { 6, 0, 5, 2, 3, 1 };
//qsort(to_sort, sizeof(to_sort) / sizeof(int), sizeof(int), comp_desc);
//print_arr(to_sort, sizeof(to_sort) / sizeof(int));
// 二分查找
qsort(to_sort, sizeof(to_sort) / sizeof(int), sizeof(int), comp_asc);
print_arr(to_sort, sizeof(to_sort) / sizeof(int));
assert(bin_search(to_sort, sizeof(to_sort) / sizeof(int), 0) == 0);
// 指針長度為8字節
// 對數組取址將會得到行指針(值與數組第0個元素地址相同),行指針+1將得到下一個行指針,即第0個元素地址+數組字節數。
int (*rp)[6] = &arr1;
assert((long long)(rp + 1) == (long long)(&arr1[0] + 6));
// 二維數組
// 二維數組空間在內存中是連續的
int r2d[3][6] = { {1, 2, 3, 4, 5, 6}, {1, 2, 3, 4, 5, 6}, {6, 5, 4, 3, 2, 1} };
print_2d_arr(r2d, sizeof(r2d) / sizeof(int[6]));
cout << (long long)&r2d[0][0] << endl;
cout << (long long)&r2d[2][5] << endl;
assert((long long)&r2d[2][5] - (long long)&r2d[0][0] == (sizeof(r2d) / sizeof(int) - 1) * sizeof(int));
}
void print_2d_arr(int rp[][6], int rows)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < 6; j++)
{
cout << "第" << i << "行第" << j << "列:" << rp[i][j] << endl;
// assert(rp[i][j] == j * j);
}
}
}
void print_arr(int arr[], int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << endl;
}
}
int comp_asc(const void* p1, const void* p2)
{
return *((int*)p1) - *((int*)p2);
}
int comp_desc(const void* p1, const void* p2)
{
return *((int*)p2) - *((int*)p1);
}
int bin_search(int arr[], int len, int target)
{
int front = 0, back = len - 1, mid = 0;
while (front <= back)
{
mid = (front + back) / 2;
if (target == arr[mid]) return mid;
else if (target < arr[mid]) back -= mid;
else front += mid;
}
return -1;
}
浙公網安備 33010602011771號