目录
4.2 lambda 表达式(匿名函数)的具体应用和使用场景
4.3 explicit 的作用(如何避免编译器进行隐式类型转换)
4.5 static 在类中使用的注意事项(定义、初始化和使用)
4.11 delete 实现原理?delete 和 delete[] 的区别?
4.12 ★new 和 malloc 的区别,delete 和 free 的区别
第四章 关键字库函数
第四章主要讲解关键字和库函数相关的内容,重点是对比功能上相似的一些关键字、库函数的区别,或者是名称上相似的关键字、库函数的区别,还对对一些关键字的用法进行讲解。
4.1 sizeof 和 strlen 的区别
面试高频指数:★★★☆☆
- strlen 是头文件 中的库函数,sizeof 是 C++ 中的运算符。
- strlen 测量的是字符串的实际长度,以 \0 结束。而 sizeof 测量的是字符数组的分配大小。
- 若字符数组 arr 作为函数的形参,sizeof(arr) 中 arr 被当作字符指针来处理,strlen(arr) 中 arr 依然是字符数组,从下述程序的运行结果中就可以看出。
-
strlen 本身是库函数,因此在程序运行过程中,计算长度;而 sizeof 在编译时,计算长度;
-
sizeof 的参数可以是类型,也可以是变量;strlen 的参数必须是 char* 类型的变量。
4.2 lambda 表达式(匿名函数)的具体应用和使用场景
面试高频指数:★★★☆☆
lambda
表达式的定义形式如下:
[capture list] (parameter list) -> reurn type
{
function body
}
其中:
- capture list:捕获列表,指 lambda 表达式所在函数中定义的局部变量的列表,通常为空,但如果函数体中用到了 lambda 表达式所在函数的局部变量,必须捕获该变量,即将此变量写在捕获列表中。捕获方式分为:引用捕获方式 [&]、值捕获方式 [=]。
- return type、parameter list、function body:分别表示返回值类型、参数列表、函数体,和普通函数一样。
举例:
lambda
表达式常搭配排序算法使用。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
vector<int> arr = {3, 4, 76, 12, 54, 90, 34};
sort(arr.begin(), arr.end(), [](int a, int b) { return a > b; }); // 降序排序
for (auto a : arr){
cout << a << " ";
}
return 0;
}
/*
运行结果:90 76 54 34 12 4 3
*/
4.3 explicit 的作用(如何避免编译器进行隐式类型转换)
面试高频指数:★★★☆☆
作用:用来声明类构造函数是显示调用的,而非隐式调用,可以阻止调用构造函数时进行隐式转换。只可用于修饰单参构造函数,因为无参构造函数和多参构造函数本身就是显示调用的,再加上 explicit 关键字也没有什么意义。
隐式转换:
#include <iostream>
#include <cstring>
using namespace std;
class A{
public:
int var;
explicit A(int tmp){ //此处关键字设置构造函数不允许进行隐式转换
var = tmp;
cout << var << endl;
}
};
int main(){
A ex(100);
A ex1 = 10; // error: conversion from 'int' to non-scalar type 'A' requested
return 0;
}
4.4 static 的作用
面试高频指数:★★★★☆
作用:
static 定义静态变量,静态函数。
- 保持变量内容持久:static 作用于局部变量,改变了局部变量的生存周期,使得该变量存在于定义后直到程序运行结束的这段时间。
- 隐藏:static 作用于全局变量和函数,改变了全局变量和函数的作用域,使得全局变量和函数只能在定义它的文件中使用,在源文件中不具有全局可见性。(注:普通全局变量和函数具有全局可见性,即其他的源文件也可以使用。)
- static 作用于类的成员变量和类的成员函数,使得类变量或者类成员函数和类有关,也就是说可以不定义类的对象就可以通过类访问这些静态成员。注意:类的静态成员函数中只能访问静态成员变量或者静态成员函数,不能将静态成员函数定义成虚函数。
4.5 static 在类中使用的注意事项(定义、初始化和使用)
面试高频指数:★★★★★
static 静态成员变量:
- 静态成员变量是在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化的时候不要出现 static 关键字和private、public、protected 访问规则。
- 静态成员变量相当于类域中的全局变量,被类的所有对象所共享,包括派生类的对象。
- 静态成员变量可以作为成员函数的参数,而普通成员变量不可以。
-
静态数据成员的类型可以是所属类的类型,而普通数据成员的类型只能是该类类型的指针或引用。
static 静态成员函数:
- 静态成员函数不能调用非静态成员变量或者非静态成员函数,因为静态成员函数没有 this 指针。静态成员函数做为类作用域的全局函数。
- 静态成员函数不能声明成虚函数(virtual)、const 函数和 volatile 函数。
4.6 static 全局变量和普通全局变量的异同
面试高频指数:★★★☆☆
相同点:
- 存储方式:普通全局变量和 static 全局变量都是静态存储方式。
不同点:
- 作用域:1)普通全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,普通全局变量在各个源文件中都是有效的;
- 2)静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。
- 初始化:静态全局变量只初始化一次,防止在其他文件中使用。
4.7 const 作用及用法
面试高频指数:★★★☆☆
作用:
- const 修饰成员变量,定义成 const 常量,相较于宏常量,可进行类型检查,节省内存空间,提高了效率。
- const 修饰函数参数,使得传递过来的函数参数的值不能改变。
- const 修饰成员函数,使得成员函数不能修改任何类型的成员变量(mutable 修饰的变量除外),也不能调用非 const 成员函数,因为非 const 成员函数可能会修改成员变量。
在类中的用法:
const 成员变量:
- const 成员变量只能在类内声明、定义,在构造函数初始化列表中初始化。
- const 成员变量只在某个对象的生存周期内是常量,对于整个类而言却是可变的,因为类可以创建多个对象,不同类的 const 成员变量的值是不同的。因此不能在类的声明中初始化 const 成员变量,类的对象还没有创建,编译器不知道他的值。
const 成员函数:
- 不能修改成员变量的值,除非有 mutable 修饰;只能访问成员变量。
- 不能调用非常量成员函数,以防修改成员变量的值。
class A{
public:
int var;
A(int tmp) : var(tmp) {}
void c_fun(int tmp) const // const 成员函数
{
var = tmp; // error: assignment of member 'A::var' in read-only object. 在 const 成员函数中,不能修改任何类成员变量。
fun(tmp); // error: passing 'const A' as 'this' argument discards qualifiers. const 成员函数不能调用非 const 成员函数,因为非 const 成员函数可能会修改成员变量。
}
void fun(int tmp){
var = tmp;
}
};
4.8 ★define 和 const 的区别
面试高频指数:★★★☆☆
区别:
- 编译阶段:define 是在编译预处理阶段进行替换,const 是在编译阶段确定其值。
- 安全性:define 定义的宏常量没有数据类型,只是进行简单的替换,不会进行类型安全的检查;const 定义的常量是有类型的,是要进行判断的,可以避免一些低级的错误。
- 内存占用:define 定义的宏常量,在程序中使用多少次就会进行多少次替换,内存中有多个备份,占用的是代码段的空间;const 定义的常量占用静态存储区的空间,程序运行过程中只有一份。
- 调试:define 定义的宏常量不能调试,因为在预编译阶段就已经进行替换了;const 定义的常量可以进行调试。
const 的优点:
- 有数据类型,在定义式可进行安全性检查。
- 可以调试。
- 占用较少的空间。
4.9 define 和 typedef 的区别
面试高频指数:★★☆☆☆
原理:
- #define 作为预处理指令,在编译预处理时进行替换操作,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。
- typedef 是关键字,在编译时处理,有类型检查功能,用来给一个已经存在的类型一个别名,但不能在一个函数定义里面使用 typedef 。
功能:
- typedef 用来定义类型的别名,方便使用。
- #define 不仅可以为类型取别名,还可以定义常量、变量、编译开关等。
作用域:
- #define 没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而 typedef 有自己的作用域。
- 指针的操作:typedef 和 #define 在处理指针时不完全一样
4.10 inline 作用及使用方法
面试高频指数:★★★☆☆
作用:
- inline 是一个关键字,可以用于定义内联函数。
- 内联函数像普通函数一样被调用,但是在调用时并不通过函数调用的机制而是直接在调用点处展开,这样可以大大减少由函数调用带来的开销,从而提高程序的运行效率。
使用方法:
1、类内定义成员函数默认是内联函数
在类内定义成员函数,可以不用在函数头部加 inline 关键字,因为编译器会自动将类内定义的函数(构造函数、析构函数、普通成员函数等)声明为内联函数。
2、类外定义成员函数,若想定义为内联函数,需用关键字声明
当在类内声明函数,在类外定义函数时,如果想将该函数定义为内联函数,则可以在类内声明时不加 inline 关键字,而在类外定义函数时加上 inline 关键字。
class A{
public:
int var;
A(int tmp){
var = tmp;
}
void fun();
};
inline void A::fun(){ //需要加上inline关键字
cout << var << endl;
}
工作原理:
- 内联函数不是在调用时发生控制转移关系,而是在编译阶段将函数体嵌入到每一个调用该函数的语句块中,编译器会将程序中出现内联函数的调用表达式用内联函数的函数体来替换。
- 普通函数是将程序执行转移到被调用函数所存放的内存地址,当函数执行完后,返回到执行此函数前的地方。转移操作需要保护现场,被调函数执行完后,再恢复现场,该过程需要较大的资源开销。
4.11 delete 实现原理?delete 和 delete[] 的区别?
面试高频指数:★★★☆☆
delete 的实现原理:
- 首先执行该对象所属类的析构函数;
- 进而通过调用 operator delete 的标准库函数来释放所占的内存空间。
delete 和 delete [] 的区别:
- delete 用来释放单个对象所占的空间,只会调用一次析构函数;
- delete [] 用来释放数组空间,会对数组中的每个成员都调用一次析构函数。
4.12 ★new 和 malloc 的区别,delete 和 free 的区别
面试高频指数:★★★★☆
在使用的时候 new、delete 搭配使用,malloc、free 搭配使用。
- malloc、free 是库函数,而new、delete 是关键字。
- new 申请空间时,无需指定分配空间的大小,编译器会根据类型自行计算;malloc 在申请空间时,需要确定所申请空间的大小。
- new 申请空间时,返回的类型是对象的指针类型,无需强制类型转换,是类型安全的操作符;malloc 申请空间时,返回的是 void* 类型,需要进行强制类型的转换,转换为对象类型的指针。
- new 分配失败时,会抛出 bad_alloc 异常,malloc 分配失败时返回空指针。
- 对于自定义的类型,new 首先调用 operator new() 函数申请空间(底层通过 malloc 实现),然后调用构造函数进行初始化,最后返回自定义类型的指针;delete 首先调用析构函数,然后调用 operator delete() 释放空间(底层通过 free 实现)。malloc、free 无法进行自定义类型的对象的构造和析构。
- new 操作符从自由存储区上为对象动态分配内存,而 malloc 函数从堆上动态分配内存。(自由存储区不等于堆)
4.13 C 和 C++ struct 的区别?
面试高频指数:★★☆☆☆
- 在 C 语言中 struct 是用户自定义数据类型;在 C++ 中 struct 是抽象数据类型,支持成员函数的定义。
- C 语言中 struct 没有访问权限的设置,是一些变量的集合体,不能定义成员函数;C++ 中 struct 可以和类一样,有访问权限,并可以定义成员函数。
- C 语言中 struct 定义的自定义数据类型,在定义该类型的变量时,需要加上 struct 关键字,例如:struct A var;,定义 A 类型的变量;而 C++ 中,不用加该关键字,例如:A var;
4.14 class 和 struct 的异同
- C++ 是在 C 语言的基础上发展起来的,为了与 C 语言兼容,C++ 中保留了
struct
。 -
struct 和 class 都可以自定义数据类型,也支持继承操作。
-
struct 中默认的访问级别是 public,默认的继承级别也是 public;class 中默认的访问级别是 private,默认的继承级别也是 private。
4.15 strcpy 函数和memcpy 函数
strcpy和memcpy都是标准C库函数,它们有下面的特点:
- strcpy函数:提供了字符串的复制。即strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符'\0'。
- memcpy函数:提供了一般内存的复制。可以复制其他任意数据类型。
void *memcpy(void *dest, const void *src, size_t n);
功能:从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。
char *a="123456789";
char b[20];
memcpy(b,a,strlen(a)+1);
//为什么要加一呢,这是因为字符串最后还有一个‘\0’结束符
//输出是根据读到‘\0’就结束输出的,所以需要把'\0'一起复制过来
____________________________________________________________
参考:
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/o5v4w6/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。