C++程序設計


程序基本概念

  1. 標識符(變量/函數名?)
    標識符由字母、數字、下划線“_”組成。不能把C++關鍵字作為標識符。標識符長度限制32字符。標識符對大小寫敏感。首字符只能是字母或下划線,不能是數字。
  2. 常量
    在定義的時候加上const, 比如const int就是整形常量。常量定義之后不可改變,
1常量用易於理解的清楚的名稱替代了含義不明確的數字或字符串,使程序更易於閱讀。
2常量使程序更易於修改。例如,在C#程序中有一個SalesTax常量,該常量的值為6%。如果以后銷售稅率發生變化,把新值賦給這個常量,就可以修改所有的稅款計算結果,而不必查找整個程序,修改稅率為0.06的每個項。
3常量更容易避免程序出現錯誤。如果把一個值賦給程序中的一個常量,而該常量已經有一個值,編譯器就回報告錯誤。
————————————————
版權聲明:本文為CSDN博主「算盤」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/codinglab/article/details/16970249

對於指針而言,如果const 在*前面,就表示指針指向的量是常量,不能通過指針來改變

int a;
const int*p = a;
*p = 5;

不合法
如果在*后面,就表示這個指針本身是常量,指向的地址不能改變

int a,b;
int *const p=a;
p=&b;

不合法
其實也很容易理解,const int * 意思是定義一個指向const int 的指針,所以不可以通過指針修改
int * const 意思是定義一個指向int的指針,這個指針本身是const 的。
所以我就會const int * const
3. 頭文件
就是#include 的那個,帶#號的表示那行是預處理指令,在編譯之前執行,比如在編譯之前會把你所有的#define都替換完成。
而#include 預處理就是把你要包含的頭文件先預處理進你的代碼里面
可以使用g++ -E main.cpp -o main.i 來看預處理完的main.i,就是你的代碼應該長的樣子,然后編譯器才會把main.i真正編譯成匯編代碼。
#include <>這樣的話庫就必須是系統自帶的,如果是自己寫的頭文件需要用#include ""
4. 命名空間
std就是一個標准的命名空間(standard)。命名空間就是什么呢,就是一個標記而已。
比如說變量“中山路”在廣州有三個,一個在荔灣區,一個在天河區,一個在增城區。如果都用變量“中山路”來表示,那么就會造成混亂。這個時候命名空間就出來了,荔灣區::中山路就表示在命名空間荔灣區里面的變量中山路,這樣就不會和天河區::中山路搞混了。
同樣的,命名空間里面不僅能定義變量,也可以定義函數,比如std::sort就是在標准命名空間std里面的sort函數。
至於怎么定義命名空間

namespace my_namespace 
{
  int a;
}
using namespace my_namespace;

注意用using namespace的話兩個namespace之間不能有同名函數、變量(重載除外)
5. 編輯、編譯、解釋、調試
編輯……編輯文本……編輯代碼,好好理解一下
編譯:把整個代碼編譯成機器語言,扔給機器執行。
解釋:(比如python)一遍掃代碼一遍把代碼每一行解釋給電腦聽,讓電腦一行行執行,所以python是有終端的,可以讓你一行行輸入一邊輸入一遍執行而c++沒有。
調試:讓程序在某個地方停下,可以觀察內部的變量情況。c++推薦使用gdb調試。

基本數據類型

整數型:32位int,64位long long
無符號整數型:32位unsigned,64位long long unsigned
實數型/浮點型:32位float,64位double,128位long double
字符型:8位char
布爾型:1位bool(但是好像也要1字節?取決於編譯器)
32位int其中一位是符號位,真正儲存數據的只有31位,所以最大可以表示0b01111111111111111111111111111111=2147483647,最小可以表示0b10000000000000000000000000000000(補碼)=-2147483648
long long 同理,32位unsigned就是那個符號位也用來存儲數據,所以最大就擴大了一倍,最小就是0
8位char其實可以理解為一個縮水的int,有些情況下為了節省內存可以當int用但是編譯器可能會警告。
(為什么不講浮點數?怕誤人子弟唄

程序基本語句

首先定義,變量類型然后后面就可以跟變量名了

int a,b;

表示定義了一個int類型的變量a,一個int類型的變量b。
cin可以用來輸入

cin >> a;

這樣就可以把輸入流的數據插入給a。同樣scanf也能輸入

scanf("%d",&a);

也可以輸入。
輸入輸出這里不再贅述,具體去看入門教程
然后賦值語句,

a = 5;

表示給變量a賦予一個值5。同樣賦予這個值也可以是另一個變量。

a = b;

表示給變量a賦予變量b的值。=成為賦值符號應該是優先級最低的運算符,運算符返回的是其右邊的值,而且是從右邊開始執行的。

a = b;

這個賦值語句是有值的!值就是b

a = b = c;

這樣也是可以的,相當於給b賦值c,然后再給a賦值b這樣其實a,b都會變成c的值。
分支語句
if(){}else{}當小括號內的值為真的時候才執行花括號里面的語句,否則執行else后面的花括號里面的語句。else及其后語句可省略,則值位假時跳過該分支語句。else會自動與最近的一個if匹配(如果這個if在其他花括號里就不會)

switch(a)
{
  case 1:
    break;
  default:
    break;
}

用小括號里面的值去匹配花括號里case后的值,如果匹配到了就從該行開始執行。如果都沒有匹配到就執行default語句及其后語句。

for(;;)
{}

小括號里通過分號分成了三個語句。執行for的開始先執行第一個語句,然后執行第二個判斷語句,如果語句為真才會執行循環內容(就是花括號里的東西)。執行完之后(或者遇到continue)就會執行第三個語句,執行完之后再判斷第二個語句……如此反復

while(){}

小括號里面為真就執行花括號的內容,知道小括號里面為假或者break跳出

do{}
while();

先執行一次花括號里面的內容,再判斷小括號的條件。如果為真就再執行一次花括號的內容,知道小括號條件為假


以上提到的多種不同用語句都可以嵌套使用。

基礎運算

加減乘整除
......
不講

整數只有整除,想要更精確得到小數結果就要轉換為小數類型再運算

res = (float)a / b;

除數和被除數任意一個為浮點數都可以。


關系運算

>
<
==
>=
<=
!=

邏輯運算
&&與,a&&b在a和b都為真時為真,否則為假。這里計算機很懶,只要a為假就不會檢測b是否為真。如果func1()&&func2(),func1()返回已經為假,那么就不會執行func2()運用這個性質可以簡化代碼。
||或,a||b在a或b隨便一個為真時為真,否則為假。同理a為真就不會檢測b是否為真。
!非,邏輯變量取反,真變為假,假變為真。!a
變量自增和自減
++和--,只能放在變量前/后
放在變量前表示執行該語句前給變量+/-1,放在變量后表示執行該語句后給變量+/-1

int a = 5;
cout << ++a;
cout << a++;
cout << a;

首先給a+1,然后輸出a。
首先輸出a然后再給a+1


三目運算
?:

a? b:c;

這是一整個式子,當a為真時整個式子的值為b,否則為c


位運算
按位與&,a&b表示二進制下每一位都執行與運算
比如2&3,就是0b10&0b11=0b10=2
按位或|,二進制下每一位都執行或運算
2|3=0b10|0b11=0b11=3
按位異或^,二進制下每一位都執行異或運算,也就是不進位的加法。
2^3=0b10|0b11=0b01=1
按位非~,二進制下每一位都取反
2=0b0010=~0b1101=-3(因為有符號位)
左移<<,二進制下整體向左移動,舍棄最高幾位。
2<<3=0b10<<3=0b10000=16
右移>>,二進制下整體向右移動,舍棄末幾位

數據庫常用函數

#include <cmath>

然后去百度
至於四舍五入函數……round()
或者floor(a+0.5)

結構化程序設計

  1. 順序結構
    從上到下按順序執行
  2. 分支結構
    如果滿足條件才執行代碼塊,否則跳過代碼塊
  3. 循環結構
    只要滿足條件就一直執行代碼塊。否則跳過代碼塊

自頂向下逐步求精的模塊化程序設計

模塊化程序設計其實有點類似於分治的思想
百度百科:在設計較復雜的程序時,一般采用自頂向下的方法,將問題划分為幾個部分,各個部分再進行細化,直到分解為較好解決問題為止。
模塊化設計,簡單地說就是程序的編寫不是一開始就逐條錄入計算機語句和指令,而是首先用主程序、子程序、子過程等框架把軟件的主要結構和流程描述出來,並定義和調試好各個框架之間的輸入、輸出鏈接關系
逐步求精的結果是得到一系列以功能塊為單位的算法描述。以功能塊為單位進行程序設計,實現其求解算法的方法稱為模塊化。模塊化的目的是為了降低程序復雜度,使程序設計、調試和維護等操作簡單化。
利用函數,不僅可以實現程序的模塊化,使得程序設計更加簡單和直觀,從而提高了程序的易讀性和可維護性,而且還可以把程序中經常用到的一些計算或操作編寫成通用函數,以供隨時調用。
比如說sort其實就是一個通用的函數,可以隨時調用。
模塊化設計又好比什么呢,我們寫高精度乘法,那么划分開就是要高精度乘一位低精度,然后高精度加法。重復執行上述步驟。
那么我們就可以先定義模塊實現高精度加法。
然后再寫高精度乘法。
那么我怎么知道要先寫什么東西呢?
這個時候流程圖就出來了

流程圖

使用圖形來表示程序執行順序
為便於識別,繪制流程圖的習慣做法是:
圓角矩形表示“開始”與“結束”;
矩形表示行動方案、普通工作環節用;
菱形表示問題判斷或判定(審核/審批/評審)環節(分支結構);
平行四邊形表示輸入輸出;
箭頭代表工作流方向。
如果箭頭指回去,毫無疑問就是循環結構。
同樣的還有PAD圖

問題分析圖(PAD)

百度:
它是一種由左往右展開的二維樹型結構.PAD圖的控制流程為自上而下,從左到右地執行。

  1. 結構清晰,層次分明,圖形標准化,而且易讀
  2. 強制設計人員使用SP方法,因而提高了產品質量
  3. 支持逐步求精的設計思想
  4. 容易將PAD圖轉換為高級語言源程序
  5. 通過機械的“走樹”可以從PAD直接產生程序,該過程便於用計算機自動實現
    比如

數組

數組(Array)是有序的元素序列。若將有限個類型相同的變量的集合命名,那么這個名稱為數組名。組成數組的各個變量稱為數組的分量,也稱為數組的元素,有時也稱為下標變量。
用於區分數組的各個元素的數字編號稱為下標。
(真的看百度吧很詳細的看到3.3停下就可以了)
要知道的就是數組里面的元素由於一次性申請所以地址是連續的(除非你溢出)
比如說int a[10];那么這10個元素的地址都是連續的,&a[0]=0x123456, &a[1]=0x12345A, &a[2]=0x12345E……以此類推,所以你只要知道一個數組第一個元素的地址,就可以通過指針加減來順序遍歷這個數組
而其實數組名a就是這個數組第一個元素的地址,不信你輸出試試。
如果你不想要這個數組了,又覺得它特別占內存,那么可以用delete[]刪掉。注意中括號里面沒有任何東西。
如果你想讓a指向一個新的空間,那么可以用new[]來重新申請一個,注意中括號里要寫常量表達式。

字符串

就是一堆字符放在一個數組里面。
可以用char*來表示。一般程序識別內存里面的字符串是從數組開頭地址開始向后讀,一直讀到有一個char的為0就結束。
如果你有char a[6]="lover!".直接cout可以得到正常字符串,如果a[3]=0;那么只能輸出lov,后面的什么都沒有了。

具體可以參考這里

函數

就是……把一堆代碼打包起來。
一般有以下三個屬性:

  • 函數名
    函數的名字……
  • 參數列表
    可有可無,有參數的函數叫做有參函數,沒有參數的函數叫做無參函數。默認參數算是參數。
    聲明的參數叫做形參,傳入的參數叫做實參。
void fun(int x){ cout << x << endl; }
fun(1);

這里聲明了執行函數fun需要一個整形參數x,這個就是形式上的參數,只要有x我就可以執行,所以這個x在函數內可以使用,只是告訴你這個參數叫做x,跟參數的具體取值沒有關系。
然后下一行調用的時候,就給fun傳入了一個1作為實際參數,即告訴fun我給你的x是1,叫做實參。

  • 返回值
    包括了返回值和返回類型,沒有返回值的時候返回類型為void
int plus_mod(int x,int y,int mod)
{
  x+=y-mod;
  x+=(x>>31)&mod;
  return x;
}
cout << plus_mod(1,2,3) << endl;

這段代碼的意思就是,定義一個函數名為plus_mod的函數,需要三個形參int x,int y,int mod,返回類型為int類型。
然后大括號里面就是函數的具體實現,最后返回了x作為返回值。
最后一行調用,把實參1,2,3傳入plus_mod函數中,執行,獲得返回值並輸出。

對於函數有沒有參數,我們可以把他分成有參函數和無參函數。對於大括號里什么東西都沒有的函數我們稱之為空函數,比如

void pass(){}

就是一個空函數
我也不知道這樣分有什么鬼用

傳值參數和傳引用參數

上面講過了,參數聲明的時候都是形式上的,唯有給他傳入值的時候才是實際上的。
那么給他傳入這個值,其實就是一個賦值的過程

x=1;
y=2;
mod=3;
x+=y-mod;
x+=(x>>31)&mod;
return x;

就是函數plus_mod的具體實現過程。
不難看出,這里是把實際參數的值復制了一份給形式參數,然后讓形參帶着這個值去執行代碼。
所以不管形參怎么變,只要他是傳值參數函數,就不會改變實際參數的值

void swap(int a,int b)
{
  a^=b^=a^=b;
}
int x=3,y=4;
swap(x,y);

這樣只是形式參數a,b的值變了,然而你傳入的實際參數x,y的值並沒有改變。
那么怎么改變傳入參數的值呢?傳入引用參數
這樣需要在函數定義的時候聲明。

void swap(int& a,int& b)
{
  a^=b^=a^=b;
}

這樣傳入表示我的參數a,b需要引用傳入,而不是復制一份。
相當於我可以直接改變x,y的值,只不過給x,y重命名而已。
我在函數體內直接操作x,y,而不是操作他們復制出來的副本。
(聽不懂?自己寫代碼看看不就知道了)

int x=3,y=4;
void swap1(int a,int b){ a^=b^=a^=b; }
void swap2(int& a,int& b){ a^=b^=a^=b; }
cout << x << ' ' << y << endl;
swap1(x,y);
cout << x << ' ' << y << endl;
swap2(x,y);
cout << x << ' ' << y << endl;

emmm...具體代碼執行順序自己調整……

常量和變量及作用范圍

常量,顧名思義就是不變的量,在c/c++中用const修飾符來聲明。如果你對const進行修改,編譯器就會告訴你非法。
但是在python里面是沒有這個機制的,一切靠程序員自覺
對於函數而言,常量就是不能動的變量。如果傳入的是常量參數,那么整個函數體都不能修改他,但是可以讀取他的值。

int plus_mod(int x,int y,const int mod)
{
  x+=y-mod;
  x+=(x>>31)&mod;
  return x;
}

這里由於模數是不需要改變的,所以用常量傳入會相對安全(雖然我也不知道安全在哪反正就是很有安全感)
傳入的參數本身不一定是常量,但是在函數體內認為是常量所以不能修改。
至於作用范圍……傳入的參數的作用范圍就是本函數,正所謂出了這家門,不是這家人。在函數體外你是不能調用函數內定義的一切東西。

遞歸

我真的不明白為什么遞歸這么難

  1. 初始狀態
    遞歸開始時候的狀態
  2. 狀態轉移
    遞歸其他狀態來求解當前狀態
  3. 遞歸邊界
    遞歸結束的條件
int f(int n)
{
  if(n<=2) return 1;
  return f(n-1)+f(n-2);
}

明顯的求解斐波那契
我承認這個算法很爛
初始狀態:求解第n項
狀態轉移:通過第\(n-1\)項和第\(n-2\)項來求解第\(n\)
遞歸邊界:第\(1\)或第\(2\)項的值為\(1\)

現在給出遞歸的百度定義:

程序調用自身的編程技巧稱為遞歸( recursion)。遞歸做為一種算法在程序設計語言中廣泛應用。 一個過程或函數在其定義或說明中有直接或間接調用自身的一種方法,它通常把一個大型復雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解,遞歸策略只需少量的程序就可描述出解題過程所需要的多次重復計算,大大地減少了程序的代碼量。遞歸的能力在於用有限的語句來定義對象的無限集合。一般來說,遞歸需要有邊界條件、遞歸前進段和遞歸返回段。當邊界條件不滿足時,遞歸前進;當邊界條件滿足時,遞歸返回。

結構體

說白了就是把一堆變量打包的數據類型
舉個例子,如果我要表示一個學生,首先一個學生肯定是變量,就像一個整型變量是變量。
但是學生是一個數據類型,就像整型是數據類型。
那么要表示一個整型變量,只需要一個整型數據就可以了。
但是要表示一個學生,需要很多數據,比如

struct Stu
{
  string name;                      // 字符串類型變量表示姓名
  int age;                          // 整型變量表示年齡
  int Chinese, Math, English;       // 整型變量表示分數
};

以及很多……

Stu Lass;

然后你就成功定義了一個叫做Stu數據類型的變量Lass
如果我要知道Lass的年齡,只需要用Lass.age訪問就好了。
(當然女生的年齡拒絕訪問)
然后我們還可以有結構體的成員函數

struct Stu
{
private:
  void show_age();
}

就表示聲明了一個讓告訴你年齡的成員函數,具體實現方法未知,但是調用這個函數就會在屏幕上輸出年齡。
然后你也看見了這個函數是私有的你不能訪問

指針

暈又是一個難搞的概念
指針很抽象來說就是地址,一個指向地址的變量。
比如說我定義了一個整型變量x,那么計算機中儲存這個變量的內存空間的位置就是我們的地址,通常用十六進制表示。
&也被稱為取地址符,使用

cout << &x << endl;

可以知道x的地址
可以很形象理解為地址就是門牌號。
你住在這里,這里就是黃泉八路東六巷橫四街1024號,就是你的地址。
如果計算機要找你,就是通過地址找到你,然后執行一波運算。
x=你
&x=黃泉八路東六巷橫四街1024號
至於指針,就是存儲你的地址的變量,

int *p=a;
p=&a;

表示定義了一個指向整型變量的指針p,定義的時候就順便初始化為指向a,也就是存儲的是a的地址。
如果定義之后也是可以改變的,就像下一行,直接賦值,p存儲a的地址。
要訪問指針指向的變量,其實就是通過地址訪問變量,在指針前面加上*就可以

int a;
int *p=a;
cout << a << endl << &a << endl << p << endl << *p << endl;

自己驗證一下啊怎么總看我胡說


然后就知道指針不止可以指向變量的嘛……
但是那些函數指針什么的我也沒用過
所以你可以發現其實函數名就是一個指針,指向內存中存儲着函數體的地址

void pass(){}
cout << pass << endl;

所以你就知道為什么sort最后傳入參數cmp不需要括號了,因為他傳入的是指針,指向那個函數。
然后其實數字名也是一個指針

int a[10];
cout << a << endl;
cout << &a[0] << endl;

他存儲了數組首元素的地址,你訪問a[i]就是訪問*(&a[0]+i),從首元素地址向后訪問i個變量。


結構體指針
通俗來說叫做鏈表……

struct Node
{
  int dat;
  Node *nxt;
}

你會發現,這個和數組差不多,只需要記住首元素是什么,然后就可以知道下一個元素的地址,訪問下一個元素。又可以訪問下下個元素……這樣的方式就叫做鏈表。這個指針是指向一個結構體類型的。
我們知道成員運算符'.'有最強的左結合性,所以你

struct Node;
Node *p;
*p.nxt;

是會被理解成

*(p.nxt);

但是p是結構體指針啊,他沒有成員變量nxt,這個時候編譯器就會報錯。
怎么辦呢?用小括號啊

(*p).nxt;

當然還有一種方便的寫法

p -> nxt;

文件

參考資料
文件的概念……沒找到……自己理解一下……
基本操作:打開、關閉文本文件或二進制文件。

文件類型

分為文本文件和二進制文件
其實文本文件就是一種特殊的二進制文件。
我們都知道文件是用數據存儲的。文本文件就是把數據按照一些規則(比如ASCII,uft-8之類的)把數據轉化為可讀文本。二進制文件就是你可以自定義規則。比如一些音頻文件,其實本質都是二進制數據存儲,但是由於解碼的方式不同,所以解碼出來就是一段好聽的音樂。如果你強行用文本文件的規則去解碼,出來就會得到一堆亂碼。
參考文件

文件讀寫

白皮書上說有三種方法

  1. 打開文件
    使用fopen函數
FILE *fopen( const char * filename, const char * mode );

表示打開路徑為filename的文件,打開方式為mode
一下是mode的一些取值

模式 描述
r 打開一個已有的文本文件,允許讀取文件。
w 打開一個文本文件,允許寫入文件。如果文件不存在,則會創建一個新文件。在這里,您的程序會從文件的開頭寫入內容。如果文件存在,則該會被截斷為零長度,重新寫入。
a 打開一個文本文件,以追加模式寫入文件。如果文件不存在,則會創建一個新文件。在這里,您的程序會在已有的文件內容中追加內容。
r+ 打開一個文本文件,允許讀寫文件。
w+ 打開一個文本文件,允許讀寫文件。如果文件已存在,則文件會被截斷為零長度,如果文件不存在,則會創建一個新文件。
a+ 打開一個文本文件,允許讀寫文件。如果文件不存在,則會創建一個新文件。讀取會從文件的開頭開始,寫入則只能是追加模式。
如果訪問的是二進制文件,只需要在這些參數后面加一個b 打開了文件之后,這個函數會返回一個文件流(不知道你怎么理解我就說是文件流 這個文件流包括了文件操作所需要的東西,學術地說是指向FILE類型的指針。 用完文件之后需要關閉文件 ```cpp int fclose( FILE *fp ); ``` 關閉成功返回0,否則返回-1 使用這些可以用文件讀寫 fprintf(FILE*,...); fscanf(FILE*,...); 就是在傳入參數的時候第一個需要傳入你的文件流,其他就和printf 和scanf一樣就可以了。 然后還有一些 ```cpp int fputc( int c, FILE *fp ); int fputs( const char *s, FILE *fp ); int fgetc( FILE * fp ); char *fgets( char *buf, int n, FILE *fp ); ``` 這些……感性理解一下就好。 P.S.:stdin,stdout其實也是FILE*類型 2. 文件流 ~~不錯又是文件流~~ cout 和 cin就是文件流 分別是ofstream和ifstream類型的 還有一個fstream類型 這里你可以理解為這些類型都是結構體,按照結構體定義就可以了 具體……參考構造函數 ```cpp ifstream infile("in.txt"); int a; if(infile.is_open()) infile << a; ``` 其實就是cout和cin的用法 不過這個文件流操作的對象(不是指向的對象它不是指針)不是stdin和stdout,而是你自己定義的。 3. 重定義輸入輸出流 ```cpp FILE* freopen(const char* filename, const char* mode, FILE* file); ``` 以mode的方式打開文件filename,把file的值重置為打開的文件並返回這個FILE* 所以freopen("","",stdin);其實只是把stdin指向的文件修改成你喜歡的文件,~~並沒有什么厲害的~~

STL 模板們

sort
很簡單吧……

template<typename _RandomAccessIterator, typename _Compare>
    inline void
    sort(_RandomAccessIterator __first, _RandomAccessIterator __last,
	 _Compare __comp)

把__first到__last里面的元素按升序排列,也就是前面比后面的“小”。
可以選擇性提供函數__comp來自行定義“小”
比如你可以定義5比3小,即a>b叫做小,這樣排序出來就是降序序列。
這里傳入的函數是函數指針

// 如果a比b“小”就返回真
bool cmp(int a,int b) { return a>b; }
int a[8];
sort(a,a+8,cmp);

具體……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM