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。
    
三、小结

持续更新中。。。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐