计算机基础知识问答:C/C++篇

  1. 讲解一下C语言内存空间的模型:
  • 代码区:它是用来存放程序执行代码的一块内存区域。通常,这部分区域是只读的,防止程序意外地修改了它的指令。
  • 常量区:数据段包含了程序中已初始化的全局变量和静态变量。而BSS段包含了程序中未初始化的全局变量和静态变量,这部分在程序开始执行前通常会被操作系统清零。
  • 堆区:堆是用于动态内存分配的区域。程序员使用malloc、calloc或realloc等函数从堆上分配内存,并使用free函数释放内存。堆是由低地址向高地址增长的。
  • 栈区:栈用于存放函数的参数值、局部变量等。每当一个函数被调用时,系统都会为其在栈上分配一块内存区域用于存储局部变量和函数调用的上下文信息。当函数返回时,这块内存区域会被释放。

图解


  1. C语言的malloc/free和C++的new/delete有哪些区别:
  • 分配方式:malloc/free是C语言提供的内存分配函数,只负责分配或释放内存,不会调用对象的构造函数或析构函数。而new/delete是C++提供的运算符,除了分配或释放内存外,还会调用对象的构造函数或析构函数。
  • 类型安全:malloc返回的是void*类型,需要显式地进行类型转换,这可能导致类型错误。而new返回的是正确类型的指针,是类型安全的。
  • 内存管理:malloc/free是C风格的内存管理,需要程序员手动管理内存,容易造成内存泄漏。而new/delete是C++风格的内存管理,会自动调用对象的构造函数和析构函数,更加安全。
  • 异常处理:在new过程中如果内存分配失败,会抛出bad_alloc异常,而malloc在内存分配失败时返回NULL。

  1. 你用过哪些C++的STL?

C++的STL(Standard Template Library)包含了很多有用的容器和算法。我常用的一些STL包括:

  • 容器:如vector、list、deque、set、map、unordered_set、unordered_map等。
  • 算法:如sort、find、binary_search、merge、unique等。
  • 迭代器:用于遍历容器中的元素。

  1. 面向对象的几大特征是什么?
  • 封装:将对象的属性和行为绑定在一起,并隐藏对象的内部状态信息,对外提供公共访问方式。
  • 继承:子类可以继承父类的属性和方法,并可以添加新的属性和方法或覆盖父类的方法。
  • 多态:允许不同类的对象对同一消息产生不同的行为。多态分为方法重载(Overloading)和方法重写(Overriding)。

  1. 讲一下继承和多继承

继承:

继承是面向对象编程中的一个重要概念,它允许我们定义一个类(称为子类或派生类)来继承另一个类(称为父类或基类)的属性和方法。子类会继承父类的所有公有(public)和保护(protected)成员,但不能继承私有(private)成员。继承可以看作是一种“is-a”关系,即子类是一种父类。继承可以是单继承(一个子类只有一个直接父类)或多继承(一个子类有多个直接父类)。


多继承:

多继承是指一个子类可以同时继承多个父类的功能。这允许子类组合多个父类的特性,从而创建更加复杂和灵活的对象。然而,多继承也带来了一些问题,如钻石问题(Diamond Problem):当两个父类都继承自一个共同的基类,并且子类同时继承这两个父类时,可能会出现基类成员的多重定义问题。为了解决这些问题,不同的编程语言提供了不同的解决方案,如虚继承(virtual inheritance)等。


  1. 函数重载和重写有什么区别?

函数重载(Overloading)和函数重写(Overriding)是面向对象编程中的两个概念,它们有着明显的区别:

函数重载(Overloading):

  • 发生在同一个类中。
  • 方法名相同,但参数列表(参数类型、数量或顺序)不同。
  • 返回值类型可以相同也可以不同。
  • 重载的函数可以有不同的访问修饰符。
  • 主要用于提供多个功能相似但操作数据不同的函数。

函数重写(Overriding):

  • 发生在子类和父类之间。
  • 子类有一个与父类中签名(方法名和参数列表)完全相同的函数。
  • 子类重写父类的方法时,返回类型必须与父类中被重写的方法的返回类型相同或是其子类。
  • 子类方法的访问修饰符不能比父类中被重写的方法的访问修饰符更严格。
  • 主要用于实现多态,子类可以以自己的方式实现父类中的方法。

  1. String.h当中包含了哪些字符串算法?

C字符串风格和C++字符串对象是不一样的

size_t strlen(const char *str);
char *strcpy(char *dest, const char *src);
char *strcat(char *dest, const char *src);
int strcmp(const char *str1, const char *str2);
char *strstr(const char *str1, const char *str2);
char *strncpy(char *dest, const char *src, size_t n);
char *strncat(char *dest, const char *src, size_t n);
int strncmp(const char *str1, const char *str2, size_t n);

  1. 指针与引用有什么区别?

指针和引用都是C++中用于间接访问对象的工具,但它们之间有一些重要的区别:

  • 初始化:指针在声明时可以不被初始化,而引用在声明时必须被初始化,并且一旦初始化后就不能再指向其他对象。
  • 空值:指针可以为nullptr,表示它不指向任何对象。而引用则不能为空,它总是指向某个对象。
  • 可复制性:指针可以被复制和赋值,指向同一个对象或不同对象。而引用一旦初始化后就不能被重新赋值指向另一个对象。
  • 可修改性:指针的内容可以改变,让它指向不同的对象。而引用的“内容”(它所引用的对象)不能改变,只能改变所引用对象的状态。

  • 取地址和间接访问:可以对指针进行取地址操作得到指针变量的地址,而对引用则不能进行取地址操作。使用*运算符可以对指针进行间接访问,得到指针所指向的值;而对引用则不需要,直接使用引用即可访问所引用的对象。
  • 指针算术:可以对指针进行算术运算,如加减一个整数,使其向前或向后移动指定的元素个数。而引用不支持这种运算。
  • 内存管理:指针需要程序员显式管理内存,包括分配和释放。而引用则不需要,它会在其引用的对象生命周期结束时自动失效。

  1. 讲一讲构造函数与析构函数?

构造函数和析构函数是C++中的特殊成员函数,它们在对象的生命周期中起着重要作用。

构造函数:构造函数是一种特殊的成员函数,它在创建对象时自动调用。它用于初始化对象的数据成员。构造函数的名称与类的名称相同,并且它没有返回类型,甚至连void也没有。构造函数可以被重载,即可以有多个构造函数,每个构造函数有不同的参数列表。默认情况下,如果程序员没有为类定义任何构造函数,编译器会提供一个默认的无参构造函数。


析构函数:析构函数也是一种特殊的成员函数,它在删除所创建的对象时自动调用。析构函数用于释放对象在生命周期中可能获取的资源,如动态分配的内存、打开的文件句柄等。析构函数的名称是在类名前加上一个波浪符~。和构造函数一样,析构函数也可以被重载,但通常一个类只需要一个析构函数。如果没有为类定义析构函数,编译器会提供一个


  1. 为什么要引入虚函数

引入虚函数(virtual functions)的主要目的是为了支持面向对象编程中的多态(polymorphism)。多态是指允许使用父类类型的指针或引用来调用在子类中重写的函数。通过虚函数,我们可以在运行时确定应该调用哪个类的函数,而不是在编译时确定。


在C++中,如果一个基类的成员函数被声明为虚函数,那么它可以在派生类中被重写。这意味着当通过基类指针或引用调用该函数时,实际调用的是派生类中的实现,而不是基类中的实现。这种机制允许我们编写更加通用和可重用的代码,因为我们可以编写操作基类指针或引用的函数,而不用担心具体是哪个派生类的对象在调用这个函数。

虚函数通过虚函数表来实现。每个包含虚函数的类都会有一个虚函数表,表中存储了指向虚函数的指针。当创建类的对象时,会在对象中存储一个指向虚函数表的指针。当通过基类指针或引用调用虚函数时,会首先查找这个指针所指向的虚函数表,然后找到对应的函数进行调用。


  1. C/C++如何实现多线程?
  • POSIX线程(Pthreads):在类Unix系统中,Pthreads是标准的多线程库。它提供了一组API来创建、同步和管理线程。
  • Windows线程:在Windows操作系统中,可以使用Windows API来创建和管理线程。这通常涉及到调用如CreateThread之类的函数。
  • C++11及以后的标准库中的:从C++11开始,标准库中包含了一个头文件,它提供了对线程的原生支持。使用std::thread类可以轻松地创建和管理线程。
  • 并发库:一些第三方库,如Intel的Threading Building Blocks (TBB),也提供了对多线程的支持,并提供了更高级别的并行算法和数据结构。

  1. 你使用过网络通信功能吗?如何在C++中编程实现网络通信?
#include <iostream>  
#include <sys/socket.h>  
#include <arpa/inet.h>  
#include <unistd.h>  
#include <string.h>  
int main() {  
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    if (sockfd < 0) {  
        std::cerr << "Error opening socket" << std::endl;  
        return 1;  
    }  
    struct sockaddr_in server_addr;  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port = htons(12345); // 服务器端口号  
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器IP地址  
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {  
        std::cerr << "Error connecting" << std::endl;  
        return 1;  
    }  

    const char *message = "Hello, server!";  
    if (send(sockfd, message, strlen(message), 0) < 0) {  
        std::cerr << "Error sending message" << std::endl;  
        return 1;  
    }  
    char buffer[1024];  
    int len = recv(sockfd, buffer, sizeof(buffer), 0);  
    if (len < 0) {  
        std::cerr << "Error receiving message" << std::endl;  
        return 1;  
    }  
    std::cout << "Received message: " << buffer << std::endl;  
    close(sockfd);  
    return 0;  
}

这个示例创建了一个TCP套接字,连接到本地主机的12345端口,发送了一条消息,并接收了服务器的响应。


  1. C/C++的并行计算框架用过哪些?都有什么特点?
  • OpenMP:OpenMP是一个用于共享内存并行编程的API,支持C、C++和Fortran。它简单易用,通过编译器指令和库函数来实现并行性。OpenMP适用于多线程共享内存环境,但不适合分布式计算。
  • MPI (Message Passing Interface):MPI是一个用于分布式内存并行计算的通信协议和编程模型。它支持不同节点之间的消息传递,常用于高性能计算和大规模并行应用。MPI提供了丰富的通信和同步原语。
  • C++11及以后的并行算法库:从C++11开始,标准库提供了并行算法支持,如std::for_each, std::transform等的并行版本。这些算法可以自动利用可用的并行性,但通常不如手动优化的并行代码高效。
  • Intel Threading Building Blocks (TBB):TBB是一个广泛使用的C++并行编程库,它提供了任务并行、数据并行和流并行等多种并行模式。TBB旨在简化并行编程,提供高效的线程池和任务调度。

  1. C++的Qt项目应该包含哪几个部分?
  • 头文件(Header Files):这些文件通常包含类的声明和定义,以及所需的Qt类和函数的前置声明。Qt使用信号和槽(signals and slots)机制进行对象间的通信,这些也通常在头文件中声明。
  • 源文件(Source Files):源文件包含实现类成员函数的具体代码。这些文件通常与头文件一一对应。
  • UI文件(User Interface Files):对于具有图形用户界面的Qt应用程序,UI文件用于定义界面布局和控件。这些文件通常以.ui为扩展名,并使用Qt Designer工具创建和编辑。

  • 资源文件(Resource Files):资源文件用于存储应用程序所需的各种资源,如图像、文本翻译文件等。资源文件通常以.qrc为扩展名。
  • 项目文件(Project Files):Qt项目通常使用.pro文件来描述项目的配置和构建规则。这个文件包含了编译器设置、源文件列表、库依赖等信息。
  • 配置文件(Configuration Files):对于某些应用程序,可能还需要配置文件来存储用户设置或应用程序状态。这些文件通常是以文本或二进制形式存储的。
  • Makefile或qmake生成的文件:当使用qmake工具处理项目文件时,会生成一个Makefile或其他构建系统所需的文件。
  • 文档和注释(Documentation and Comments):对于大型项目,良好的文档和注释是必不可少的。

  1. 你平常碰到bug怎么进行调试?有哪些常见手段?
  • 打印日志(Logging):在代码的关键部分添加打印语句,输出变量的值或函数调用的结果,以了解程序的执行流程和状态。
  • 使用调试器(Debugger):集成开发环境(IDE)通常都内置了调试器,允许你在程序运行时暂停执行、查看变量值、单步执行等。调试器是定位和解决bug的强大工具。
  • 简化问题(Simplifying the Problem):尝试简化代码或问题,将其拆分成更小的部分,以便更容易地定位问题所在。
  • 使用断言(Assertions):在代码中添加断言来检查预期条件是否满足,如果不满足则触发错误。这可以帮助快速识别代码中的逻辑错误。
posted @ 2024-04-07 21:41  白纸画卷水墨如冰  阅读(2)  评论(0编辑  收藏  举报