【C++语言基础】总结篇

点我–>C++内存
点我–>面向对象
点我–>STL
点我–>新特性

【操作系统】常见问题汇总及解答
【数据库】常见问题汇总及解答
【计算机网络】常见问题汇总及解答

1. 简述C++语言的特点

  • C++在C语言的基础上引入了面对对象的机制,同时也兼容C语言
  • C++有三大特性:封装、继承、多态;
  • C++语言编写出的程序结构清晰、易于扩充,程序可读性好;
  • C++生成的代码质量高,运行效率高,仅比汇编语言慢10%~20%;
  • C++更加安全,增加了 const 常量、引用、四类 cast 转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)、智能指针、try - catch等;
  • C++可复用性高,C++引入了模板的概念,后面在此基础上,实现了方便开发的标准模板库 STL(Standard Template Library);
  • 同时,C++是不断发展的语言。C++后续版本更是发展了不少新特性,如C++11中引入了 nullptr、auto 变量、Lambda匿名函数、右值引用、智能指针。

2. 说说C语言和C++的区别

  • C语言是C++的子集,C++可以很好地兼容C语言;但是C++又有很多新特性,如引用、智能指针、auto变量等;
  • C++是面向对象的编程语言,C语言是面向过程的编程语言;
  • C语言有一些不安全的语言特性,如指针使用的潜在危险、强制转换的不确定性、内存泄露等;而C++对此增加了不少新特性来改善安全性,如const常量、引用、cast 转换、智能指针、try - catch 等;
  • C++可复用性高,C++引入了模板的概念,后面在此基础上,实现了方便开发的标准模板库STL,C++的STL库相对于C语言的函数库更灵活、更通用。

3. 说说C++中 struct 和 class 的区别

  • 访问控制:在struct中,成员默认是公共的(public),而在class中,默认是私有的(private)。也就是说,在class中,必须显式指定访问级别才能访问该成员。例如:
struct S {
    
    
    int x;  // 默认:公共成员
};

class C {
    
    
    int x;  // 默认:私有成员
public:
    void setX(int value) {
    
     x = value; }  // 需要显式设置public访问级别
    int getX() {
    
     return x; }
};
  • 继承方式:在class中,默认继承方式是私有继承(private),而在struct中,是公共继承(public)。例如:
struct Base {
    
    
    void foo() {
    
    
        std::cout << "Base::foo()" << std::endl;
    }
};

struct Derived1 : Base {
    
     };   // 公共继承
class Derived2 : Base {
    
     };   // 私有继承

int main() {
    
    
    Derived1 d1;
    d1.foo();  // OK,公共继承

    Derived2 d2;
    d2.foo();  // 错误,私有继承
}

可以看到,Derived1从Base中继承的成员函数foo()可以在Derived1中直接使用,而Derived2则不能。

  • class 关键字可以用于定义模板参数,就像 typename,而 struct 不能用于定义模板参数。

4. 说说include头文件的顺序以及双引号""和尖括号<>的区别

  • 区别:
    • 尖括号<>的头文件是系统文件,双引号""的头文件是自定义文件
    • 编译器预处理阶段查找头文件的路径不一样。
  • 查找路径:
    • 使用尖括号<>的头文件的查找路径:编译器设置的头文件路径–>系统变量。
    • 使用双引号""的头文件的查找路径:当前头文件目录–>编译器设置的头文件路径–>系统变量。

5. 说说C语言结构体和C++结构体的区别

  • C语言的结构体内不允许有函数存在,C++允许有内部成员函数,且允许该函数是虚函数。
  • C语言的结构体对内部成员变量的访问权限只能是public,而C++允许public、protected、private三种。
  • C语言的结构体是不可以继承的,C++的结构体是可以从其他的结构体或者类继承过来的。
  • C语言中使用结构体需要加上 struct 关键字,或者对结构体使用 typedef 取别名,而C++中可以省略 struct 关键字直接使用。例如:
struct Student
{
    
    
	int age;
	string name;
};

struct Student stu1; // C语言中正常使用

typedef struct Student Student2; // C语言中取别名
Student2 stu2; // C语言中通过取别名的使用

Student stu3; // C++中使用
对比项 C语言 C++
成员函数 不能有 可以
静态成员 不能有 可以
访问控制 默认public,不能修改 public/private/protected
继承关系 不能继承 可从类或者其他结构体继承
初始化 不能直接初始化数据成员 可以

6. 说说导入C函数的关键字是什么,C++编译时和C语言有什么不同

  • 关键字:在C++中,导入C函数的关键字是extern,表达形式为extern “C”, extern “C” 的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
  • 编译区别:由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名
// extern示例:在C++程序里边声明该函数,会指示编译器这部分代码按C语言的进行编译
extern "C" int strcmp(const char* s1, const char* s2);

// 在C++程序里边声明该函数
extern "C" 
{
    
    
	#include <string.h> //string.h里边包含了要调用的C函数的声明
} 
//两种不同的语言,有着不同的编译规则,比如一个函数fun,可能C语言编译的时候为_fun,而C++则是__fun__

7. 简述C++从代码到可执行二进制文件的过程

C++和C语言类似,一个C++程序从源码到执行文件,有四个过程:预编译、编译、汇编、链接。

  • 预编译:这个过程主要的处理操作如下:
    • 将所有的#define删除,并且展开所有的宏定义;
    • 处理所有的条件预编译指令,如#if、#ifdef;
    • 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置;
    • 过滤所有的注释;
    • 添加行号和文件名标识。
  • 编译:这个过程主要的处理操作如下:
    • 词法分析:将源代码的字符序列分割成一系列的记号;
    • 语法分析:对记号进行语法分析,产生语法树;
    • 语义分析:判断表达式是否有意义;
    • 代码优化;
    • 目标代码生成:生成汇编代码;
    • 目标代码优化。
  • 汇编:这个过程主要是将汇编代码转变成机器可以执行的指令。
  • 链接:将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。链接分为静态链接和动态链接:
    • 静态链接:是在链接的时候就已经把要调用的函数或者过程链接到了生成的可执行文件中,就算你再去把静态库删除也不会影响可执行程序的执行;生成的静态链接库,Windows下以 .lib 为后缀,Linux下以 .a 为后缀。
    • 动态链接:是在链接的时候没有把调用的函数代码链接进去,而是在执行的过程中,再去找要链接的函数,生成的可执行文件中没有函数代码,只包含函数的重定位信息,所以当你删除动态库时,可执行程序就不能运行。生成的动态链接库,Windows下以 .dll 为后缀,Linux下以 .so 为后缀。

8. 说说 static 关键字的作用

  • 定义全局静态变量和局部静态变量:在变量前面加上 static 关键字。初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS(未初始化的数据)段分配内存。直到程序结束,静态变量始终会维持前值。只不过全局静态变量和局部静态变量的作用域不一样。
  • 定义静态函数:在函数返回类型前加上 static 关键字,函数即被定义为静态函数。静态函数只能在本源文件中使用。
  • 在变量类型前加上static关键字,变量即被定义为静态变量。静态变量只能在本源文件中使用
  • 在C++中,static关键字可以用于定义类中的静态成员变量:使用静态数据成员,它既可以被当成全局变量那样去存储,但又被隐藏在类的内部。类中的 static 静态数据成员拥有一块单独的存储区,而不管创建了多少个该类的对象。所有这些对象的静态数据成员都共享这一块静态存储空间。
  • 在C++中,static关键字可以用于定义类中的静态成员函数:与静态成员变量类似,类里面同样可以定义静态成员函数。只需要在函数前加上关键字static 即可。如静态成员函数也是类的一部分,而不是对象的一部分。所有这些对象的静态数据成员都共享这一块静态存储空间。

「注意」:当调用一个对象的非静态成员函数时,系统会把该对象的起始地址赋给成员函数的this指针。而静态成员函数不属于任何一个对象,因此C++规定静态成员函数没有this指针(重点)。既然它没有指向某一对象,也就无法对一个对象中的非静态成员进行访问。

9. 说说数组和指针的区别

  • 定义不同:数组是用于储存多个相同类型数据的集合,数组名是首元素的地址。指针相当于一个变量,但是它和其它变量不一样,它存放的是其它变量在内存中的地址,指针名指向了内存的首地址。
  • 内存分配:数组的内存分配是静态的,这意味着数组的大小在编译时就已经确定了。指针可以动态地分配内存,这意味着指针在运行时可以使用malloc()或类似函数动态地分配内存。
  • 初始化:数组可以在定义时初始化,也可以在之后进行初始化。指针在定义时可以初始化为NULL,但不会分配内存。
  • 使用:数组可以直接访问元素,例如,可以将数组的第一个元素称为arr[0]。指针必须先解除引用,才能访问指向的对象,例如,可以通过*p来访问指向的对象。
  • 算术运算:指针可以进行算术运算,如指向下一个地址、上一个地址、加上一个整数等。这些运算符在指针操作中非常实用。而数组的不同元素之间是可以进行算术运算的,但它们必须以整数为单位,并且指向的内存地址必须是连续的。
  • 赋值:同类型指针变量可以相互赋值;数组不行,只能一个一个元素的赋值或拷贝。
  • 求sizeof:求数组所占存储空间的内存大小为 sizeof(数组名) / sizeof(数据类型)。在32位平台下,无论指针的类型是什么,sizeof(指针名) 都是4,在64位平台下,无论指针的类型是什么,sizeof(指针名) 都是8。

10. 说说什么是函数指针,如何定义函数指针,有什么使用场景

  • 函数指针是指向函数的指针,即它包含函数的地址。函数指针可以像普通指针一样传递给函数,也可以用作函数返回值。使用函数指针可以方便地调用不同的函数,以匹配参数和返回值类型。
  • 在C++中,定义函数指针需要指定函数名、参数类型和返回类型。例如,以下是一个指向接受两个整数参数并返回一个整数的函数的指针的定义:
int (*funcPtr)(int, int);

这个定义中,funcPtr是指向函数的指针,它指向的函数接受两个整数参数并返回整数。要使用函数指针,需要将其初始化为一个函数的地址。例如,以下是将funcPtr初始化为函数add的地址的示例:

#include <iostream>
using namespace std;

int add(int x, int y) {
    
    
    return x + y;
}

int main() {
    
    
    int (*funcPtr)(int, int) = &add;
    int result = (*funcPtr)(3, 4); // 调用函数指针
    cout << "result = " << result << endl; // result = 7
    return 0;
}

在这个示例中,funcPtr被初始化为add函数的地址,然后通过在前面加上*来调用该函数指针,并传递两个整数参数(3和4)。最终结果存储在result变量中。

  • 使用函数指针的常见场景包括动态地选择要在运行时调用的函数、作为回调函数使用(例如在事件处理中)以及在函数模板中使用。函数指针还可以用于实现函数指针数组和函数指针结构体,以提供更灵活的程序结构。
    回调(callback):我们调用别人提供的API函数(Application Programming Interface,应用程序编程接口),称为Call;如果别人的库里面调用我们的函数,就叫Callback。

11. 说说静态变量什么时候初始化

  • 对于C语言的全局和静态变量,初始化发生在任何代码执行之前,属于编译期初始化。
  • 而C++标准规定:全局或静态对象当且仅当对象首次用到时才进行构造。

12. 说说 nullptr 可以调用成员函数吗,为什么

能。因为在编译时对象就绑定了函数地址,和指针空不空没关系。

示例:

#include<iostream>
using namespace std;

class Animal
{
    
    
public:
	void sleep()
	{
    
    
		cout << "Animal sleep()" << endl;
	}
	void breathe()
	{
    
    
		cout << "Animal breathe()" << endl;
	}
};

class Fish :public Animal
{
    
    
public:
	void breathe()
	{
    
    
		cout << "Fish breathe()" << endl;
	}
};

int main()
{
    
    
	Animal* ani = nullptr;
	ani->breathe(); // Animal breathe()

	Fish* fis = nullptr;
	fis->breathe(); // Fish breathe()

	return 0;
}

原因:因为在编译时对象就绑定了函数地址,和指针空不空没关系。ani->breathe();编译的时候,函数的地址就和指针ani绑定了;调用breath(*this), this就等于ani。由于函数中没有需要解引用this的地方,所以函数运行不会出错,但是若用到this,因为this=nullptr,运行出错。

13. 说说什么是野指针,怎么产生的,如何避免

  • 概念:野指针(dangling pointer)是指一个指向已释放或未分配内存区域的指针。当程序尝试使用野指针时,会导致不可预测的行为,例如崩溃、数据损坏等严重问题。
  • 野指针通常是由以下几种情况引起的
    • 操作已释放的内存,如 delete 一个指针并尝试在之后使用它。
    • 操作未分配的内存,如使用未初始化的指针。
    • 操作一个已经超出作用域的指针,如在函数内部分配了一个局部变量的内存,然后从函数返回时返回指向该内存的指针。
  • 避免野指针的方法
    • 在释放内存后,要将指针赋值为nullptr(空指针)。这可以防止将一个指针用于已经释放的内存或未分配的内存。
    • 避免使用未初始化的指针,通常可以通过将指针初始化为nullptr。
    • 在使用指针之前,始终先检查其是否为nullptr。这可以帮助避免访问未分配或已释放的内存。
    • 如果指针只用于访问函数内的局部变量,则应在函数退出时将其设为nullptr。
    • 谨慎使用C语言的内存分配函数,在分配内存后,始终检查指针是否为 nullptr。并正确释放分配的内存。
    • 使用智能指针。

14. 说说静态局部变量,全局变量,局部变量的特点,以及使用场景

  1. 静态局部变量

静态局部变量是指在函数内定义的变量,但是在函数的执行过程中它的值保持不变。静态局部变量只初始化一次,并且只能在定义该变量的函数内部访问。这种类型的变量会保留在程序的内存中,直到程序结束。静态局部变量的定义格式为:

static int count = 0;

静态局部变量的特点:

  • 生命周期长:静态局部变量只被初始化一次,在函数调用结束之后,该变量的值将被保留。
  • 只能在定义该变量的函数内部访问:静态局部变量只能在定义该变量的函数内部访问,不能被其他函数使用。
  • 默认值为0:没有被初始化的静态局部变量会被默认赋值为0。
  • 在编译的时候被分配地址:静态局部变量在编译的时候就被赋予了地址,所以其访问速度很快。

静态局部变量的使用场景:

  • 在函数内部需要维护一个计数器等,每次调用函数后需要保留上次的结果,可以使用静态局部变量。
  1. 全局变量

全局变量是定义在函数外的变量,它可以在任何函数内部被访问。全局变量创建后一直存在于程序运行的整个生命周期内,除非被程序显式地销毁。全局变量的定义格式为:

int count = 0;

全局变量的特点:

  • 生命周期长:全局变量一旦被定义,就会一直存在于整个程序的运行期间。
  • 可以在程序任何地方访问:全局变量可以在程序的任何地方被访问,但是在局部变量的同名情况下,局部变量会被优先访问。
  • 需要谨慎使用:全局变量为所有函数共享,容易产生命名冲突、数据污染等问题,所以需要谨慎使用。

全局变量的使用场景:

  • 全局变量可以存储全局的配置、状态、计数等信息,可以被所有函数访问。
  • 全局变量可以用于在程序中传递数据,比如需要在不同的函数中共享相同的数据。
  1. 局部变量

局部变量是定义在函数内部的变量,它只能在定义的函数内部被访问,离开函数作用域后就会被自动销毁。局部变量的定义格式为:

int count = 0;

局部变量的特点:

  • 生命周期短:局部变量在函数的执行过程中随着函数调用的结束而结束,出了函数作用域后自动销毁。
  • 只能在定义函数内部访问:局部变量只能在它所属的函数内被访问,外部函数无法访问。
  • 可以同名不同值:在不同的函数内可以声明同名的局部变量,变量名不必唯一。

局部变量的使用场景:

  • 在函数内部需要定义一些临时变量,在函数执行结束后自动销毁,可以使用局部变量。
  • 在不同的函数内需要声明相同的变量名,但是不同的变量赋予不同的值,可以使用局部变量。

15. 说说内联函数和宏函数的区别

C++内联函数和宏函数都可以用来在程序中替换代码块,提高代码的执行效率。但是,它们有一些区别:

  • 内联函数是由编译器处理的函数,编译器将内联函数的代码复制到调用它的代码处,以便避免函数调用时的额外开销。而宏函数则是简单的字符串替换,编译器不会处理宏函数的代码,而是将它直接替换为它的定义。
  • 内联函数可以处理复杂的参数类型和表达式,而宏函数不行。内联函数会检查参数类型是否正确,而宏函数则不会,这可能会导致类型不匹配的错误。
  • 内联函数可以像普通函数一样具有局部变量和控制语句,而宏函数则不行。内联函数是由编译器处理的代码块,可以像普通函数一样包括局部变量、if语句、while循环等。但是,由于宏函数是简单的代码替换,它不会创建局部变量,不会处理if语句和while循环等控制语句。
  • 内联函数定义在头文件中,可以被多个源文件调用,而宏函数则可以在任何地方定义,但只能在当前文件中使用。

下面是内联函数和宏函数的代码示例:

  • 内联函数示例
#include <iostream>
using namespace std;

// 定义内联函数
inline int add(int a, int b)
{
    
    
    return a + b;
}

int main()
{
    
    
    int a = 5, b = 3;
    // 调用内联函数
    int c = add(a, b);
    cout << "a + b = " << c << endl;
    return 0;
}
  • 宏函数示例
#include <iostream>
using namespace std;

// 宏函数的定义
#define ADD(a, b) ((a) + (b))

int main()
{
    
    
    int a = 5, b = 3;
    // 调用宏函数
    int c = ADD(a, b);
    cout << "a + b = " << c << endl;
    return 0;
}

可以看出,使用宏函数和内联函数的语法有些不同。使用内联函数时,需要在函数声明前加上inline关键字;而宏函数则直接用#define定义。但是,在实际编程过程中,应该尽量避免使用宏函数,因为它可能会导致像类型不匹配、语义不明确等问题,而内联函数则更安全、更易于使用。

使用时的一些注意事项

  • 使用宏定义一定要注意错误情况的出现,比如宏定义函数没有类型检查,可能传进来任意类型,从而带来错误。还有就是括号的使用,宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性。
  • inline函数一般用于比较小的,频繁调用的函数,这样可以减少函数调用带来的开销。只需要在函数返回类型前加上关键字inline,即可将函数指定为inline函数。
  • 同其它函数不同的是,最好将inline函数定义在头文件,而不仅仅是声明,因为编译器在处理inline函数时,需要在调用点内联展开该函数,所以仅需要函数声明是不够的。

内联函数使用的条件

  • 内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。以下情况不宜使用内联:
    • 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
    • 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
  • 内联不是什么时候都能展开的,一个好的编译器将会根据函数的定义体,自动地取消不符合要求的内联。

16. 说说运算符i++和++i的区别

  • 赋值顺序不同:++i 是先加后赋值;i++ 是先赋值后加。++i 和 i++都是分两步完成的。
  • 效率不同:后置++执行速度比前置的慢。
  • i++ 不能作为左值,而 ++i 可以。
int i = 0;
int* p1 = &(++i);//正确
// int* p2 = &(i++);//错误
++i = 1;//正确
// i++ = 1;//错误
  • 两者都不是原子操作。

17. 说说 new 和 malloc 的区别,各自底层实现原理

两者的区别

  • new 是操作符,而 malloc 是函数。
  • new 在调用的时候先分配内存,再调用构造函数,释放的时候调用析构函数;而 malloc 没有构造函数和析构函数。
  • malloc 需要给定申请内存的大小,返回的指针需要强转;new 会调用构造函数,不用指定内存的大小,返回指针不用强转。
  • new 可以被重载,malloc 不行。
  • new 分配内存更直接和安全。
  • new 发生错误抛出异常,malloc 返回null。

malloc底层实现:当开辟的空间小于128K时,调用 brk() ;当开辟的空间大于128K时,调用 mmap() 。malloc采用的是内存池的管理方式,以减少内存碎片。先申请大块内存作为堆区,然后将堆区分为多个内存块。当用户申请内存时,直接从堆区分配一块合适的空闲快。采用隐式链表将所有空闲块,每一个空闲块记录了一个未分配的、连续的内存地址。

new底层实现:关键字 new 在调用构造函数的时候实际上进行了如下的几个步骤:

  • 创建一个新的对象;
  • 将构造函数的作用域赋值给这个新的对象(因此this指向了这个新的对象);
  • 执行构造函数中的代码(为这个新对象添加属性);
  • 返回新对象。

18. 说说 const 和 define 的区别

const 用于定义常量,而 define 用于定义宏,而宏也可以用于定义常量。都用于常量定义时,它们的区别有:

  • const 生效于编译的阶段,define 生效于预处理阶段。
  • const 定义的常量,在C语言中是存储在内存中、需要额外的内存空间的。define 定义的常量,运行时是直接的操作数,并不会存放在内存中。
  • const 定义的常量是带类型的,define 定义的常量不带类型。因此 define 定义的常量不利于类型检查。
  • 定义域不同:const 常量只在定义域内有效,而 define 宏不受定义域限制。

19. 说说C++中函数指针和指针函数的区别

  1. 函数指针的定义和用法

函数指针是指向函数的指针,它用来存储函数的地址。在C++中,函数指针的定义格式为:

return_type (*pointer_name)(parameter1_type, parameter2_type, ...);

其中,return_type 表示函数的返回值类型,pointer_name 是指针的名称,parameter1_type、parameter2_type 等则是函数参数的类型。

函数指针的用法主要有两种:

  • 将函数指针作为函数的参数传递:
void function(int parameter, void (*pointer_name)(int)) {
    
    
    pointer_name(parameter);
}
  • 将函数指针作为变量使用:
int add(int x, int y) {
    
    
    return x + y;
}

int main() {
    
    
    int (*func_ptr)(int, int);
    func_ptr = add;
    int result = func_ptr(1, 2);
    return 0;
}

在上面的例子中,我们先定义了一个函数指针 func_ptr,然后将函数 add 的地址赋值给它。接着,我们通过 func_ptr 来调用函数 add 并计算出结果。

从使用方式上来看,函数指针主要用于将函数作为数据传递、动态调用函数以及函数指针数组的构建。

  1. 指针函数的定义和用法

指针函数是返回指针类型的函数,它的返回值是一个指针。在C++中,指针函数的定义格式为:

data_type* function_name(parameter1_type, parameter2_type, ...)

其中,data_type 表示指针所指向的数据类型,function_name 是函数名称,parameter1_type、parameter2_type 等则是函数参数的类型。

指针函数的用法可以简单地归纳为以下两类:

  • 返回指向堆内存的指针:
int* create_array(int size) {
    
    
    int* arr = new int[size];
    return arr;
}

在上面的例子中,我们使用 new 关键字来创建一个动态数组,并返回它的指针。

  • 返回指向静态或全局内存的指针:
char* get_message() {
    
    
    static char message[] = "Hello world";
    return message;
}

在上面的例子中,我们定义了一个静态字符数组 message,并将其作为指针函数的返回值。

从使用方式上来看,指针函数主要用于返回指向堆内存或静态/全局内存的指针,以及处理复杂的数据结构和对象。

「总结」:函数指针和指针函数虽然都与指针有关,但是它们的含义和用法却大不相同。函数指针主要用于将函数作为数据传递、动态调用函数以及函数指针数组的构建,而指针函数主要用于返回指向堆内存或静态/全局内存的指针,以及处理复杂的数据结构和对象。

20. 说说const int *a, int const *a, const int a, int *const a, const int *const a分别是什么,有什么特点

const int a;          // a是一个常量,不允许修改
const int* a;         // a指针所指向的内存里的值不变,即 (*a) 不变
int const* a;         // 同 const int *a;
int* const a;         // a指针所指向的内存地址不变,即a不变
const int* const a;   // 都不变,即 (*a) 不变,a也不变

21. 说说使用指针需要注意什么

  • 指针空值检查:在使用指针之前,需要检查它是否为 NULL。如果指针是NULL,则表示该指针不指向任何有效的内存位置,进一步操作可能导致程序崩溃。
  • 指针悬空问题:指针悬空是指指针指向的内存位置已经被释放或已经失效,但是指针仍然指向该位置,这时候对指针进行取值或者修改,会导致未知的错误。
  • 指针越界:当指针操作的内存范围超出了分配给它的内存区域,就会导致指针越界,这可能会导致程序崩溃或者数据被破坏。
  • 内存泄漏:使用动态分配的内存时,需要确保在使用完毕后进行释放,否则会导致内存泄漏,使得系统的可用内存变少。
  • 多级指针:如果使用多级指针,需要确保正确理解指针的级别及其所指向的内存区域,以避免出现不必要的错误。
  • 指针运算:在使用指针操作时,需要确保运算的正确性和安全性,否则会破坏内存数据,导致程序崩溃。
  • 指针类型:在声明和使用指针时,需要确保指针类型与其所指向的内存位置的类型相匹配,否则会导致数据类型不匹配的错误。
  • 指针别名:指针别名是指两个或多个指针指向同一内存位置,这时候如果对一个指针进行修改,会影响其它指针所指向的内存位置,这可能会导致不必要的错误。

22. 说说内联函数和函数的区别,内联函数的作用

区别

  • 内联函数比普通函数多了关键字 inline
  • 内联函数避免了函数调用的开销,普通函数有调用的开销;
  • 普通函数在被调用的时候,需要寻址(函数入口地址),内联函数不需要寻址;
  • 内联函数有一定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句,普通函数没有这个要求。

内联函数的作用:内联函数在调用时,是将调用表达式用内联函数体来替换。避免函数调用的开销。

「注意」:

  • 在内联函数内不允许用循环语句和开关语句:如果内联函数有这些语句,则编译将该函数视同普通函数那样产生函数调用代码,递归函数是不能被用来做内联函数的。内联函数只适合于只有1~5行的小函数。对一个含有许多语句的大函数,函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现。
  • 内联函数的定义必须出现在内联函数第一次被调用之前。
  • 内联声明只是建议,是否内联由编译器决定,所以实际并不可控。

23. 简述C++有几种传值方式,之间的区别是什么

传参方式有三种:值传递、引用传递、指针传递。

  • 值传递:形参即使在函数体内值发生变化,也不会影响实参的值;
  • 引用传递:形参在函数体内值发生变化,会影响实参的值;
  • 指针传递:在指针指向没有发生改变的前提下,形参在函数体内值发生变化,会影响实参的值。

「总结」:值传递用于对象时,整个对象会拷贝一个副本,这样效率低。而引用传递用于对象时,不发生拷贝行为,只是绑定对象,更高效。指针传递同理,但不如引用传递安全。

代码示例:

#include <iostream>
using namespace std;

void testfunc(int a, int* b, int& c) 
{
    
    
	// 形参a的值发生了改变,但是没有影响实参i的值
	// 但形参*b、c的值发生了改变,影响到了实参*j、k的值
	a += 1;
	(*b) += 1;
	c += 1;
	cout << "a = " << a << endl;  // 2
	cout << "b = " << *b << endl; // 2
	cout << "c = " << c << endl;  // 2
} 

int main() 
{
    
    
	int i = 1;
	int a = 1;
	int* j = &a;
	int k = 1;
	testfunc(i, j, k);
	cout << "i = " << i << endl;  // 1
	cout << "j = " << *j << endl; // 2
	cout << "k = " << k << endl;  // 2

	return 0;
}

24. 简述指针常量与常量指针的区别

  • 指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其他地方不能改变。 常量指针是指定义了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值。
  • 指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变性。
// const* 是常量指针,*const 是指针常量
int const* a; // a指针所指向的内存里的值不变,即 (*a) 不变
int* const a; // a指针所指向的内存地址不变,即a不变

25. 变量的声明和定义有什么区别

  • 声明(variable declaration):指变量的类型和名称在程序中的标识。通常发生在头文件中,在主函数之外进行。它告知编译器变量的存在,但并没有为其分配任何存储空间。在声明一个变量时,编译器会查找变量的类型以及名称,并将其添加到符号表中。

例如,以下代码中的 x 和 y 只是被声明了,但没有给它们赋值。

int x;
double y;
  • 定义(variable definition):除了声明外,还会给变量分配存储空间。在定义变量时,编译器会为变量分配内存空间,该空间大小取决于变量类型的大小。同时,变量在定义时可以进行初始化或被赋值。

例如,以下代码将会定义两个 int 类型的变量 x 和 y,并对它们进行了初始化。

int x = 10; 
int y(20);
  • 区别:变量声明表明了变量的类型和名称,但不会为其分配内存空间;变量定义除了声明变量的类型和名称之外,还要为其分配内存空间,并可以为变量赋初值。一个变量可以在多个地方声明, 但是只在一个地方定义。加入 extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。

26. 写出int 、bool、 float 、指针变量与 “零值”比较的 if 语句

「注意」:题目中要求的是零值比较,而非与0进行比较,在C++里“零值”的范围很大,可以是0, 0.0 , FALSE或者“空指针”。

下面是答案:

//int与零值比较 
if ( n == 0 )
if ( n != 0 )

//bool与零值比较 
if   (flag)   //表示flag为真 
if   (!flag)  //表示flag为假 

//float与零值比较 
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON)) //其中EPSINON是允许的误差(即精度)

//指针变量与零值比较 
if (p == NULL)
if (p != NULL)

猜你喜欢

转载自blog.csdn.net/m0_51913750/article/details/130319060