C++面试基础知识整理(3)

inline内联函数

使用

// 类内定义,隐式内联
class A {
    
    
    int doA() {
    
     return 0; }         // 隐式内联
}

// 类外定义,需要显式内联
class A {
    
    
    int doA();
}
inline int A::doA() {
    
     return 0; }   // 需要显式内联

问题

内联函数和宏函数的区别

  • 内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。
  • 内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
  • 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
  • 内联函数在运行时可调试,而宏定义不可以。

缺点

  • 代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
  • 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。

虚函数可以是内联函数吗

  • 虚函数可以是内联函数,但是当虚函数表现多态性的时候不能内联。
  • 内联是在编译器编译期间确定的,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。

explicit

作用

  • explicit 修饰构造函数时,可以防止隐式转换和复制初始化。

  • explicit关键字只需用于类内的单参数构造函数前面。

使用

struct A
{
    
    
	A(int) {
    
     }
}

struct B
{
    
    
	explicit B(int) {
    
    }
    explicit B(int i, int j) {
    
    } // explicit关键字在类构造函数参数大于或等于两个时无效
    explicit B(int i, int j=10) {
    
    } // 当除了第一个参数以外的其他参数都有默认值的时候, explicit关键字依然有效
};

int main(){
    
    
	A a1(1);		// OK:直接初始化
	A a2 = 1;		// OK:复制初始化
    // 隐式转换具体细节:编译器先将常量1构造出一个临时A对象,然后再拷贝构造给a2
    
    B b1(1);		// OK:直接初始化
	B b2 = 1;		// 错误:被 explicit 修饰构造函数的对象不可以隐式转换
}

sizeof

问题

class A
{
    
    
};
class B
{
    
    
public:
    B() {
    
    }
    ~B() {
    
    }
};
class C
{
    
    
public:
    C() {
    
    }
    virtual ~C() {
    
    }
};

int main()
{
    
    
    A a;
    B b;
    C c;
    cout << sizeof(a) << endl;
    cout << sizeof(b) << endl;
    cout << sizeof(c) << endl;
    return 0;
}

输出结果是多少

  • 1 1 4
  • 空类型的实例不包含任何信息,本应为0.但是当声明该类型的实例时,必须在内存中占有一定空间,否则无法使用这个实例。至于占用内存的多少,由编译器决定。
  • 构造函数和析构函数的地址只与类型相关,与类型的实例无关。编译器不会因为这两个函数而在实例内添加任何信息。
  • 编译器一旦发现一个类型中有虚函数,就会为该类型生成虚函数表,并在该类型的每个实例中添加一个指向虚函数表的指针。在32位机器上,一个指针占4字节空间,在64位机器上,占8字节空间。
int GetSize(int data[])
{
    
    
    return sizeof(data);
}

int main()
{
    
    
    int data1[]={
    
    1,2,3,4,5};
    cout<<sizeof(data1)<<endl;
    
    int *data2=data1;
    cout<<sizeof(data2)<<endl;
    
    char data3[]="12345";
    cout<<sizeof(data3)<<endl;
    
    cout<<GetSize(data1)<<endl;
}

输出结果是多少

  • 20 4 6 4
  • data1是个数组,sizeof(data1)是求数组的大小。这个数组包含5个整数,每个整数占4个字节,因此共占用20字节。
  • data2为一个指针,尽管指向了数组data1的第一个数字,但其本质仍然是一个指针。
  • data3是一个数组,要注意字符数组隐含的末尾元素‘\0‘。
  • 当数组作为函数的参数进行传递时,数组就自动退化为同类型的指针,因此data3本质仍是一个指针。
  • sizeof 对数组,得到整个数组所占空间大小;sizeof 对指针,得到指针本身所占空间大小。

memcpy与memmove

  • memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝N个字节到目标dst所指的内存地址的起始位置中。

  • memmove函数的功能同memcpy基本一致,但是当src区域和dst内存区域重叠时,memcpy可能会出现错误,而memmove能正确进行拷贝

  • memcpy与memmove

字符串相关

strlen

  • 不包括字符串结尾’\0’
  • 注意与sizeof求字符数组长度的区别,sizeof会包含末位字符’\0‘

strcpy

  • strcpy会从源地址一直往后拷贝,直到遇到’\0’为止。
  • stcpy不允许有内存重叠的情况且必须有足够的空间容纳下待复制的字符串。

编码实现strcpy

    char * strcpy( char *strDest, const char *strSrc ) 
    {
    
    
    	assert( (strDest != NULL) && (strSrc != NULL) );
    	char *address = strDest; 
    	while( (*strDest++ = * strSrc++) != ‘\0); 
    	return address;
    }
    char str[10]="abc";
    my_strcpy(str+1,str);
  • 正确实现如下:
    char *my_strcpy(char *dst,const char *src)
    {
    
    
    	assert(dst != NULL);
    	assert(src != NULL);
    	char *ret = dst;
    	memmove(dst,src,strlen(src)+1);
    	return ret;
    }
  • memmove函数实现时考虑到了内存重叠的情况,可以完成指定大小的内存拷贝

  • strcpy函数的实现

lambda表达式

  • lambda表达式就是一个函数(匿名函数),也就是没有函数名的函数。为什么不需要函数名呢,因为我们直接(一次性的)用它,嵌入式用的它,不需要其他地方调用它。

  • lambda表达式也叫闭包,闭就是封闭的意思,就是其他地方都不用他,包就是函数。

  • lambda表达式 其实就是一个函数对象,他内部创建了一个重载()操作符的类。

    [ capture-list ] ( params ) -> ret {
    
     body }
  • 其中( params ) -> ret定义了这个匿名函数的参数和返回类型, { body }定义了这个匿名函数的功能,捕捉列表[ capture-list ]使这个匿名函数可以访问外部(父作用域)变量。
    int main() {
    
    
        int a = 0;
        auto f = ([a]()->void {
    
    cout << a << endl;});
        f();
        return 0;
    }
  • 捕捉方式有按值和按引用两种。比如[a, &b]表示按值捕捉a,按引用捕捉b;[&, a]表示按引用捕捉所有父作用域变量,除了a按值捕捉,[=,&b]表示按值捕捉所有父作用域变量,除了b按引用捕捉。

C++中的Lambda表达式

函数指针

  • 待补充

智能指针

作用

  • 智能指针主要用于管理在堆上分配的内存。当一个智能指针指向对象的生命周期结束后,会自动释放申请的内存,从而防止内存泄漏。

  • 动态内存管理经常会出现三种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针;一种是同一块内存释放两次。

  • shared_ptr

    • 采用引用计数的方法,记录当前内存资源被多少个智能指针引用。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。

    • 独有操作:

        shared_ptr<T> p(q); // p是q的拷贝,此操作会递增q中的计数器
        p = q; // 此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则释放其管理的原内存。
  • weak_ptr

    • weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
    • 一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。
    • weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。
  • unique_ptr:某个时刻只能有一个unique_ptr指向一个给定对象。不支持普通的拷贝或复制操作。

详解C++11智能指针

猜你喜欢

转载自blog.csdn.net/qq_34731182/article/details/113419784
今日推荐