C++中的易混點
一、前言
還有4天,今天繼續回顧易混的點。。。
二、易混的點
多態性的含義
誤解:多態性指的是對象的狀態會根據運行時要求自動變化
正解:同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果。
闡釋:多態是針對對象沒錯,但是前提是針對同一個消息/操作
在除法運算中,保證精度
如何保證精度呢?只是將承載變量定義為float或double就能實現了嗎?答案當然是No!!!。我指條明路:
float c = 5.0f / 4;
float c = 5 / 0.4f;
float c = 5.0 / 4;
float c =(float)5 / 4;
不同繼承下的成員訪問權限
|
列表示繼承方式
|
|
|
|
|
|
|
|
不可訪問
|
|
|
|
|
不可訪問
|
|
|
|
|
不可訪問
|
記了很多遍了,但今天再看,又是“熟悉的陌生人”…
關於指針的運算
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。
三、小結
持續更新中。。。