C++面试题

C++中各种特性

总目录

1.static

static 是一个关键字,它可以用于不同的场景,在 C++ 中是一个相当有用的关键字。下面是对其主要用途的简要概述:

  1. 修饰局部变量:将局部变量声明为 static,使其在函数调用之间保持其值。因此,该变量的生命周期与程序的生命周期相同,而不是只在函数调用期间存在
#include <iostream>

void increment() {
   
    static int count = 0; // 静态变量,在函数调用结束后不会被销毁,保持其值
    count++;
    std::cout << "Count is: " << count << std::endl;
}

int main() {
   
    increment(); // 输出:Count is: 1
    increment(); // 输出:Count is: 2
    increment(); // 输出:Count is: 3
    return 0;
}

  1. 修饰全局变量:当 static 修饰全局变量时,只有在声明该变量的文件中才可以使用该变量。这样可以在多个文件中使用具有相同名称的全局变量,而不会出现命名冲突的问题。
// file1.cpp
#include <iostream>

static int globalVariable = 10;

void function1() {
   
    std::cout << "globalVariable in file1.cpp: " << globalVariable << std::endl;
}

// file2.cpp
#include <iostream>

extern int globalVariable; // 引用file1.cpp中的globalVariable

void function2() {
   
    std::cout << "globalVariable in file2.cpp: " << globalVariable << std::endl;
}

int main() {
   
    function1(); // 输出:globalVariable in file1.cpp: 10
    function2(); // 输出:globalVariable in file2.cpp: 10
    return 0;
}

在这个示例中,globalVariable被声明为静态,因此只能在file1.cpp中访问。在file2.cpp中,通过extern关键字引用了globalVariable,以便在file2.cpp中使用它。

  1. 修饰函数:当 static 修饰函数时,将其限制为该文件内部的函数,即该函数只能在声明它的文件中调用,不能在其他文件中调用。
// file1.cpp
#include <iostream>

static void foo() {
   
    std::cout << "foo() in file1.cpp" << std::endl;
}

// file2.cpp
#include <iostream>

void bar() {
   
    std::cout << "bar() in file2.cpp" << std::endl;
}

int main() {
   
    foo(); // 非法调用,会报错
    bar(); // 合法调用
    return 0;
}

在这个示例中,foo函数被声明为静态,因此只能在file1.cpp文件中调用。bar函数没有被声明为静态,因此可以在其他文件中调用。

  1. 修饰类成员变量:当 static 修饰类的成员变量时,这些变量将成为类本身的变量,而不是类的实例的变量。这些成员变量只有一个副本,供所有类的实例使用。因此,可以将这些变量用于类的所有实例之间的共享数据。
#include <iostream>

class MyClass {
   
public:
    static int staticVar;//定义静态类成员变量
    MyClass(int val) : value(val) {
   }
    void printValues() {
   
        std::cout << "value: " << value << ", staticVar: " << staticVar << std::endl;
    }

private:
    int value;
};

int MyClass::staticVar = 0; // 静态成员变量的定义和初始化

int main() {
   
    MyClass obj1(10);
    MyClass obj2(20);

    obj1.printValues(); // 输出:value: 10, staticVar: 0
    obj2.printValues(); // 输出:value: 20, staticVar: 0

    MyClass::staticVar = 5; // 直接通过类名访问静态成员变量

    obj1.printValues(); // 输出:value: 10, staticVar: 5
    obj2.printValues(); // 输出:value: 20, staticVar: 5

    return 0;
}

  1. 修饰类成员函数:当 static 修饰类的成员函数时,可以将其视为独立于类的实例的函数,即,不需要通过类的实例来调用。因此,可以使用类名来访问它们,而不必创建类的实例
#include <iostream>

class MyClass {
   
public:
    static void staticFunction() {
   
        std::cout << "Static function called" << std::endl;
    }

    void normalFunction() {
   
        std::cout << "Normal function called" << std::endl;
    }
};

int main() {
   
    MyClass::staticFunction(); // 直接调用静态函数,输出:Static function called

    MyClass obj;
    obj.normalFunction(); // 输出:Normal function called

    // MyClass::normalFunction(); // 错误,静态函数无法访问非静态成员
    // obj.staticFunction(); // 错误,对象无法访问静态函数

    return 0;
}

2.const

在C++中,const关键字用于声明常量,它可以用于不同的上下文,包括:

  1. 常量变量(Constant Variables):声明变量时,使用const关键字可以将其声明为常量,使得其值在初始化后不能被修改。例如:

    const int MAX_VALUE = 100;
    
  2. 常量指针(Constant Pointers):将指针声明为const可以防止修改指针所指向的变量,但不阻止修改指针本身。例如:

    int value = 10;
    const int *ptr = &value; // 指向常量的指针,不能通过ptr修改value的值
    
  3. 常量引用(Constant References):使用const修饰引用,可以创建对常量的引用,防止通过引用修改变量的值。例如:

    int value = 10;
    const int &ref = value; // 对value的常量引用,不能通过ref修改value的值
    
  4. 成员函数中的const(Const Member Functions):在类中声明成员函数时,如果函数不修改对象的状态,则应将其声明为const成员函数。这样做可以确保在const对象上调用该函数时不会修改对象的状态。例如:

    class MyClass {
         
    public:
        int getValue() const {
          return value; } // const成员函数,不修改对象的状态
    private:
        int value;
    };
    

总的来说,const关键字用于声明常量,防止修改变量的值或者防止修改指针或引用所指向的变量,以及声明const成员函数以确保不修改对象的状态。

  1. 需要注意的是,const 在修饰变量、函数参数和函数返回值时,都可以使用在左侧或右侧,但有一些微妙的差别。例如:
    const int *p 和 int const *p 是等价的,它们都表示指向常量的指针。
    int * const p 表示指针是常量,即 p 不能指向其他地址,但 p 指向的地址中的值可以被修改。
    const int * const p 表示指向常量的常量指针,既不能修改指针的值,也不能修改指针所指向的地址中的值。

3.面向对象的三大特征,封装性、继承性和多态性和public,private、protected:

  1. 封装性:将客观事物抽象成类,每个类对自身的数据和方法实行 protection(private, protected, public)。
    类的属性public、private和protected
  • 类的public成员可以被任意实体访问,你可以认为它就是c语言中的struct结构体,可以直接用a.x这种形式访问;
  • 类的private成员不能直接被类的实体访问,也不能被子类的实体访问,但是可以被类的成员函数访问;
  • 类的protected成员不能直接被类的实体访问,但是可以被子类访问,也可以被类的成员函数访问;
  1. 继承性:广义的继承有三种实现形式:实现继承(使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。

  2. 多态性:是将父类对象设置成为和一个或更多它的子对象相等的技术。用子类对象给父类对象赋值之后,父类对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。 这部分需要熟悉掌握原理虚函数,了解一些概念(静态多态、动态多态)等,面试时经常会问。

    说明:面向对象的三个特征是实现面向对象技术的关键,每一个特征的相关技术都非常的复杂,程序员应该多看、多练。

3.1.封装

在C++中,privateprotectedpublic是用于指定类成员的访问权限的三个访问修饰符。

  1. private

    • 私有成员只能被同一个类中的成员函数访问,无法被类的对象或者其他类的成员函数直接访问。
    • 私有成员对于类的用户来说是不可见的,因此无法直接访问或者修改。
  2. protected

    • 受保护成员可以被同一个类中的成员函数以及派生类中的成员函数访问。
    • 受保护成员对于类的用户来说是不可见的,但可以在派生类中被访问。
  3. public

    • 公有成员可以被任何函数访问,包括同一个类中的成员函数、派生类中的成员函数以及类的用户代码。
    • 公有成员对于类的用户来说是可见的,并且可以直接访问。

下面是一个示例说明它们之间的区别:

class MyClass {
   
private:
    int privateMember;

protected:
    int protectedMember;

public:
    int publicMember;

    void accessPrivate() {
   
        privateMember = 10; // 合法,访问私有成员
    }

    void accessProtected() {
   
        protectedMember = 20; // 合法,访问受保护成员
    }

    void accessPublic() {
   
        publicMember = 30; // 合法,访问公有成员
    }
};

class DerivedClass : public MyClass {
   
public:
    void accessProtectedFromDerived() {
   
        protectedMember = 40; // 合法,在派生类中访问受保护成员
    }
};

int main() {
   
    MyClass obj;
    obj.publicMember = 50; // 合法,访问公有成员

    // obj.privateMember = 60; // 错误,私有成员不可访问
    // obj.protectedMember = 70; // 错误,受保护成员不可访问

    DerivedClass derivedObj;
    // derivedObj.protectedMember = 80; // 错误,受保护成员不可访问

    return 0;
}

在这个示例中,privateMember是私有成员,在MyClass外部无法访问。protectedMember是受保护成员,可以在MyClass和其派生类中访问,但在类的外部是不可见的。publicMember是公有成员,可以在任何地方访问。

3.2.什么叫做多态性?在 C++种是如何实现多态的?

多态是指同样的消息被不同类型的对象接收时导致完全不同的行为,是对类的特定成员函数的再抽象。
C++支持重载多态,强制多态,包含多态和参数多态。 在基类中声明相应的函数为 virtual 型,然后在派生类中实现该函数,这样就可以通过基类指针调用派生类对象的函数,实现了运行时动态绑定,即多态的功能。

#include <iostream>
using namespace std;

class Shape {
   
   protected:
      int width, height;

   public:
      Shape( int a = 0, int b = 0) {
   
         width = a;
         height = b;
      }
      virtual int area() {
   
         cout << "Parent class area :" <<endl;
         return 0;
      }
};

class Rectangle: public Shape {
   
   public:
      Rectangle( int a = 0, int b = 0):Shape(a, b) {
    }

      int area () {
   
         cout << "Rectangle class area :" <<endl;
         return (width * height);
      }
};

class Triangle: public Shape{
   
   public:
      Triangle( int a = 0, int b = 0):Shape(a, b) {
    }
      
      int area () {
   
         cout << "Triangle class area :" <<endl;
         return (width * height / 2);
      }
};

int main() {
   
   Shape *shape;
   Rectangle rec(10,7);
   Triangle tri(10,5);
   
   // 存储矩形的地址
   shape = &rec;
   // 调用矩形的求面积函数 area
   shape->area();
   // 存储三角形的地址
   shape = &tri;
   // 调用三角形的求面积函数 area
   shape->area();
   return 0;
}

在此示例程序中,Shape 是一个基类,Rectangle 和 Triangle 是两个派生类,它们都继承了 Shape 类的数据成员和成员函数。Shape 类中定义了 virtual 关键字的虚函数 area(),在派生类中对其进行了重写(即覆盖)。在 main() 函数中,首先创建了一个基类指针 shape,然后将其分别指向 Rectangle 和 Triangle 对象。在调用 shape->area() 函数时,程序在运行时动态地选择了哪个类的函数将被调用,因此使得程序具有了多态性。

3.2.为什么说”继承是 C++面向对象的主要特征之一”?请简要说明.?

继承是一种联结类的层次模型,层次结构的上层是最具有通用性的,而下层的部分,即后代具有特殊性。类可以从他的祖先那里继承方法和成员变量,也可以增加新的方法是适用于特殊的需要。如果没有继承,类就缺失了一块层次结构,代码重用和数据共享就贯彻不到底,有了继承,就会有抽象编程中的多态问题,只有从机制内部真正解决了多态表现问题,对象的数据封装,信息隐藏,代码重用等招式才能淋漓尽致的发挥出来。才称得上真正的面向对象编程。

4.C中的 malloc 和C++中的 new 有什么区别

  1. new、delete 是操作符,可以重载,只能在 C++中使用。
  2. malloc、free 是函数,可以覆盖,C、C++中都可以使用。
  3. new 可以调用对象的构造函数,对应的 delete 调用相应的析构函数。
  4. malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
  5. new、delete 返回的是某种数据类型指针,malloc、free 返回的是 void 指针。

new创建对象会调用构造函数,delete删除对象会调用析构函数

5.析构函数

我们已经知道构造函数是在创建对象时,对其进行初始化。而析构函数与其相反,是在对象被删除前象由系统自动执行它做清理工作。作为一个类,可能有多个对象,每个对象生命结束时都要调用析构函数,且每个对象调用一次。

特点:
(1)无类型
(2)无返回值
(3)名字与类名相同
不带参数,不可重载,析构函数只有一个!
析构函数前“~” (取反符,表示逆构造函数)
作用:在对象被删除前做清理工作。

注意:对象的析构函数在对象被销毁前被调用,对象何时销毁也与其作用域相关。

例如,全局对象是在程序运行结束时销毁;
自动对象是在离开其作用域时销毁;
而动态对象是在使用delete运算符时销毁。

析构函数特别适用于当一个对象被动态分配内存空间,而在对象被销毁前希望释放它所占用的内存空间的时候。我们不会忽略初始化的重要性,却常常忽略清除的重要性,然而对销毁变量的内存清理是非常重要的。

例如,我们在堆中申请了一些内存,如果没有用完就释放,会造成内存泄露,会导致应用程序运行效率降低,甚至崩溃,不可掉以轻心。

而在c++中提供有析构函数,可以保证对象清除工作自动执行。
析构与构造的调用次序相反,即最先构造的最后被析构,最后构造的最先被析构。

6.比较值传递和引用传递

值传递:是指当发生函数调用时,给形参分配内存空间,并用实参来初始化形参(直接将实参的值传递给形参)。
这一过程是参数值的单向传递过程,一旦形参获得了值便与实参脱离关系,此后无论形参发生了怎样的改变,都不会影响到实参。

引用传递:将引用作为形参,在执行主调函数中的调用语句时,系统自动用实参来初始化形参。这样形参就成为实参的一个别名,对形参的任何操作也就直接作用于实参。

7.什么叫内联函数?它有哪些特点?

定义时使用关键字 inline 的函数叫做内联函数;编译器在编译时在调用处用函数体进行替换,节省了参数传递、控制转移等开销;
内联函数体内不能有循环语句和 switch 语句;
内联函数的定义必须出现在内联函数第一次被调用之前;对内联函数不能进行异常接口声明;

内联函数:

inline const string& longerStr(const string& str1, const string& str2)
{
   
	return str1.size() > str2.size() ? str1 : str2;
}

当我们试图打印输出调用结果时:

cout << longerStr(str1, str2) << endl;

编译器会自动把它展开为:

cout << (str1.size() > str2.size() ? str1 : str2) << endl;

这样就大大提高了运行效率。

8.构造函数和析构函数

构造函数:

1、构造函数概念
一个类的对象被创建的时候,编译系统对象分配内存空间,并自动调用该构造函数,由构造函数完成成员的初始化工作。因此,构造函数的核心作用就是,初始化对象的数据成员

2、构造函数的特点
(1)名字与类名相同,可以有参数,但是不能有返回值(连void也不行)。
(2)构造函数是在实例化对象时自动执行的,不需要手动调用。
(3)作用是对对象进行初始化工作,如给成员变量赋值等。
(4)如果定义类时没有写构造函数,系统会生成一个默认的无参构造函数,默认构造函数没有参数,不做任何工作。
(5)如果定义了构造函数,系统不再生成默认的无参构造函数.
(6)对象生成时构造函数自动调用,对象一旦生成,不能在其上再次执行构造函数
(7)一个类可以有多个构造函数,为重载关系。

3、构造函数的分类

  • 按参数种类分为:无参构造函数、有参构造函数、有默认参构造函数
  • 按类型分为:普通构造函数、拷贝构造函数(赋值构造函数)
//声明Time类 
class Time
{
   
public:  //成员函数共有部分 
	Time()  //定义构造成员函数,函数名与类名相同 
	{
   
		hour= 0;  //利用构造函数给对象中的数据成员都赋初值为0 
		minute= 0;
		sec= 0;
	}
	//成员函数的声明
	void set_time(); 
	void show_time(void);
private:  //类的私有数据部分 
	int hour;  //默认数据也是私有的 
	int minute;
	int sec; 
};

析构函数

我们已经知道构造函数是在创建对象时,对其进行初始化。而析构函数与其相反,是在对象被删除前象由系统自动执行它做清理工作。
作为一个类,可能有多个对象,每个对象生命结束时都要调用析构函数,且每个对象调用一次。

特点:

  • 无类型
  • 无返回值
  • 名字与类名相同

不带参数,不可重载,析构函数只有一个!
析构函数前“~” (取反符,表示逆构造函数)
作用:在对象被删除前做清理工作。

注意:对象的析构函数在对象被销毁前被调用,对象何时销毁也与其作用域相关。
例如,全局对象是在程序运行结束时销毁;
自动对象是在离开其作用域时销毁;
而动态对象是在使用delete运算符时销毁。

析构函数特别适用于当一个对象被动态分配内存空间,而在对象被销毁前希望释放它所占用的内存空间的时候。我们不会忽略初始化的重要性,却常常忽略清除的重要性,然而对销毁变量的内存清理是非常重要的。

例如,我们在堆中申请了一些内存,如果没有用完就释放,会造成内存泄露,会导致应用程序运行效率降低,甚至崩溃,不可掉以轻心。
而在c++中提供有析构函数,可以保证对象清除工作自动执行。
析构与构造的调用次序相反,即最先构造的最后被析构,最后构造的最先被析构。

9.虚函数、纯虚函数

虚函数:虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数,是C++中多态性的一个重要体现利用基类指针访问派生类中的虚函数,这种情况下采用的是动态绑定技术。

纯虚函数:纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”.纯虚函数不能实例化对象。

Shape 是一个抽象类,它定义了两个抽象方法 getArea 和 getPerimeter。这两个方法只是定义了接口,没有任何实现。
Rectangle 类继承自 Shape,并实现了父类的抽象方法,从而具体化了 Shape 接口。
class Shape {
   
public:
    virtual double getArea() = 0; // 纯虚函数,表示这是一个接口,需要子类来实现
    virtual double getPerimeter() = 0;
};

class Rectangle : public Shape {
   
public:
    Rectangle(double width, double height): m_width(width), m_height(height) {
   }
    virtual double getArea() override {
    return m_width * m_height; 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值