C++中的易混點

一、前言

還有4天,今天繼續回顧易混的點。。。

二、易混的點

多態性的含義

誤解:多態性指的是對象的狀態會根據運行時要求自動變化

正解同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果。

闡釋:多態是針對對象沒錯,但是前提是針對同一個消息/操作

在除法運算中,保證精度

如何保證精度呢?只是將承載變量定義為floatdouble就能實現了嗎?答案當然是No!!!。我指條明路:

float c = 5.0f / 4;
float c = 5 / 0.4f;
float c = 5.0 / 4;
float c =(float)5 / 4;
不同繼承下的成員訪問權限

列表示繼承方式

public

protected

private

public

public

protected

不可訪問

protected

protected

protected

不可訪問

private

private

private

不可訪問

記了很多遍了,但今天再看,又是“熟悉的陌生人”…

關於指針的運算
int a[10]={1,2,3,4,5,6,7,8,9,10};
*p=a;
// 第一種
*p+=9;					// 先對p解引用得到具體的值——a[0],然後 + 9,使得a[0] = 10
// 第二種
b = *(p+8);				// 讓p指針移動8個位置,指向a[8]的內存地址,再解引用,得到a[8]:9
open成員函數

open成員函數的作用是:將一個流對象與特定的文件關聯起來,而不是生成新的流對象。

cout.fill()

C++ 輸出流的一個成員函數。

填充字符設置是持久的,可能會影響後續輸出。除非被再次fill而覆蓋

動態聯編
  • 定義
    聯編 指的是將函數調用語句與具體的函數定義代碼連接(綁定)在一起的過程。它解決的是“當程序執行到 obj->func()時,到底應該執行哪一段 func的代碼?”這個問題。
  • 類別
    動態聯編:在程序運行階段才根據對象的實際類型來確定調用哪個函數。也稱為晚期綁定。
#include <iostream>
using namespace std;

class Animal {
public:
    // 關鍵:使用 virtual 關鍵字
    virtual void speak() {
        cout << "Animal speaks" << endl;
    }
};

class Dog : public Animal {
public:
    // 重寫虛函數(override關鍵字是C++11的好習慣,用於檢查)
    void speak() override {
        cout << "Woof!" << endl;
    }
};

class Cat : public Animal {
public:
    void speak() override {
        cout << "Meow!" << endl;
    }
};

int main() {
    Dog myDog;
    Cat myCat;

    Animal* animalPtr;

    animalPtr = &myDog;
    animalPtr->speak(); // 輸出 "Woof!" - 正確!

    animalPtr = &myCat;
    animalPtr->speak(); // 輸出 "Meow!" - 正確!

    return 0;
}

分析:現在程序在運行時檢查 animalPtr實際指向的對象類型,並調用該類型對應的 speak方法。

靜態聯編:在程序編譯鏈接階段就確定了調用哪個函數。也稱為早期綁定。

#include <iostream>
using namespace std;

class Animal {
public:
    void speak() {
        cout << "Animal speaks" << endl;
    }
};

class Dog : public Animal {
public:
    void speak() { // 隱藏(不是重寫)了基類的 speak
        cout << "Woof!" << endl;
    }
};

class Cat : public Animal {
public:
    void speak() {
        cout << "Meow!" << endl;
    }
};

int main() {
    Dog myDog;
    Cat myCat;

    myDog.speak(); // 輸出 "Woof!" - 正確
    myCat.speak(); // 輸出 "Meow!" - 正確

    // 問題場景:使用基類指針
    Animal* animalPtr;

    animalPtr = &myDog;
    animalPtr->speak(); // 輸出 "Animal speaks" - 不符合預期!

    animalPtr = &myCat;
    animalPtr->speak(); // 輸出 "Animal speaks" - 不符合預期!

    return 0;
}

分析:在編譯階段,編譯器看到 animalPtr的類型是 Animal*,所以它毫不猶豫地將 speak()調用綁定到了 Animal::speak()。這就是靜態聯編。它不管運行時 animalPtr實際指向的是狗還是貓。

關於++ --

增1和減1運算符只能作用於左值(變量、數組元素、對象成員等),不能作用於表達式或右值。

深入剖析函數傳參
int swap(int &a, int &b);
int x = 10, y = 20;
swap(x+2, y+5);  // ❌ 編譯錯誤
swap(x++, ++y);  // ❌ 編譯錯誤
  • 第一個錯誤
  • x+2是一個算術表達式,計算結果是一個臨時值(右值)
    解決之道:右值引用:void process(int &&a, int &&b)
  • 這個臨時值沒有持久的內存地址,運算完成後就被丟棄
  • swap函數的參數是 int &a(非常量左值引用)
  • 非常量左值引用不能綁定到右值
  • 第二個錯誤
    參數1:x++(後綴遞增)
  • x++返回的是 x舊值(10)
  • 這個返回值是一個右值(臨時值)
  • 同樣無法綁定到非常量左值引用 int &a

參數2:++y(前綴遞增)

  • ++y返回的是 y引用(左值)
  • 理論上可以綁定到 int &b
  • 但問題在於參數求值順序不確定
左值VS右值
  • 左值:可以出現在賦值號左邊的表達式(有標識符,有內存地址)
  • 右值:只能出現在賦值號右邊的表達式(臨時值,沒有持久內存地址)
外部類變量(extern)和外部靜態類變量(static)的作用域
  • extern變量:具有外部鏈接,作用域是整個程序
  • static變量:具有內部鏈接,作用域僅限於當前文件
右移 >>補位規則

右移運算符(>>):補位規則取決於操作數類型

  • 無符號數:空位補0 ✅
  • 有符號數:空位補0還是補1取決於編譯器實現(通常是符號位擴展)❌
正數右移前: 76 (二進制: 01001100)
正數右移後: 19 (二進制: 00010011)  // 左側補0
負數右移前: -100 (二進制: 10011100)  
負數右移後: -25 (二進制: 11100111)  // 左側補1(符號位擴展)
枚舉 enum

默認第一個元素為0,其他元素被賦值就取賦值的數,沒有賦值就根據前面的元素 + 1

ASCII

a是97,A是65

位運算

若是x為4,y為9,則(~x)^y的結果是( -14)。

運算過程

計算 ~x(按位取反)
x:  0000 0000 0000 0000 0000 0000 0000 0100
~x: 1111 1111 1111 1111 1111 1111 1111 1011
計算 (~x) ^ y(按位異或)	異或(相同為0,不同為1)
~x: 1111 1111 1111 1111 1111 1111 1111 1011
y:  0000 0000 0000 0000 0000 0000 0000 1001
	1111 1111 1111 1111 1111 1111 1111 0010
這是補碼錶示的負數
題目要十進制結果,可以轉換:
這個二進制是負數,先取反加 1 得到絕對值:
取反:0000 0000 0000 0000 0000 0000 0000 1101
加 1:0000 0000 0000 0000 0000 0000 0000 1110 = 14
所以原數是 -14。
三、小結

持續更新中。。。