c/c++面试题(1、new和malloc的区别2、c++中四种cast转换3、头文件的几种常见结构)

一、回答new和malloc的区别

主要参考这位大神的回答:经典面试题之new和malloc的区别

1、属性

  • new/delete 是C++的关键字需要编译器支持。
  • malloc/free是库函数,需要头文件支持。

2、参数

  • 使用new操作符申请分配内存空间时,无需指定内存块的大小,编译器会自动根据类型信息进行计算。
  • 以下,都是初始化一个值为0的整形变量
int *a = new int();
int *a = new int(0);
int *a = new int{};
int *a = new int{0};
  • 而malloc则需要显示的指出所需内存的尺寸;
  • 示例如下:
void *malloc(size_t size)	//声明 参数:size -- 内存块的大小,以字节为单位。

char *str;
str = (char *) malloc(15);	//内存分配15字节
strcpy(str, "runoob");
printf("String = %s,  Address = %u\n", str, str);

3、返回类型

  • new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无需进行类型转换,so new 是符合类型安全的操作符。
  • 而malloc内存分配成功则返回void*,需要通过强制类型转换将void*指针转换成我们需要的类型。

4、分配失败

  • new分配失败时,会抛出bac_alloc异常。
  • malloc分配失败时,返回NULL。

5、自定义类型

  • new会先调用 operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。
  • delete 会先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
  • malloc/free是库函数,只能动态申请和释放内存,无法强制要求其做自定义类型对象的构造和析构工作。

6、重载

  • c++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。
  • 严格意义上来讲不是对new/delete关键字的重载,编译器会将new/delete操作符转化成调用operator new()/operator delete()函数,实际上重载的是这两个函数。
  • 而malloc不允许重载。

7、内存区域

  • new操作符从自由存储区(free store)上为对象动态分配内存空间。自由存储区是C++基于new操作符的一个抽象概念(不是实际的操作系统内存控件),凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配。
  • C语言使用malloc函数从堆上动态分配内存,使用free释放已分配的对应内存。
  • 自由存储区不等于堆,例如上文中的布局new就可以不位于堆中。
  • 堆是操作系统中一个实际的区域,而自由存储区是更上层的一个概念。绝大多数情况下,new确实是在堆上申请内存,但是程序员也可以通过重载new运算符,使用堆以外的内存来实现自由存储。
  • 也可以这样理解,在C语言中malloc分配内存空间使用堆的概念。而在C++中new分配内存空间则使用自由存储区的概念。
  • 关于free store and heap的详细解释,可以看下这位大神写的:“free store” VS “heap”

8、既生瑜何生亮-有了malloc为什么还要new?

  • malloc是面向内存的,你需要多大的内存空间,就给你分配多大的内存空间,不参与其他工作。
  • new是面向对象的,根据你指定的数据类型(包括自定义数据类型)来申请对应的空间,还可以自动的调用构造函数初始化成员变量。
  • 对于内部数据类型(基本数据类型)而言,malloc已经足够了。但是对于非内部数据类型而言,malloc不能满足动态对象的要求。而且还需要在创建/消亡时,自动执行构造/析构函数,这就是出现new/delete的原因。
  • 而且malloc/free是库函数,编译器并不能操作,而对于操作符new/delete则可以增加他们工作。

二、c++中四种cast转换

1、C风格强制类型转换

C风格的强制转换(Type Cast)容易理解,不管什么类型的转换都可以使用。
代码如下(示例):

TypeName b = (TypeName)a;

C++支持C风格的强制转换,但是C风格的强制转换是有隐患的。所有的转换形式都一样,让一些问题难以察觉,所以C++提供了四种在不同场景下的强制转换函数。

2、const_cast

  • 常量指针被转换成非常量的指针,并且仍然指向原来的对象;
  • 常量引用被转换成非常量的引用,并且仍然指向原来的对象;
  • const_cast一般用于修改指针。例如:const char *p形式;
//--------------------------------------------示例1
    int arr[4] = { 1,2,3,4 };
    for(int i = 0; i < 4; i++)
    {
        cout << arr[i] << "\t";
    }
    cout << endl;

    const int *c_ptr = arr;
    //    c_ptr[1] = 23;                    //error: read-only variable is not assignable
    int *ptr = const_cast<int*>(c_ptr); //将常量指针转为非常量指针就可以进行操作了

    for(int i = 0; i < 4; i++)
    {
        ptr[i] += 1;
    }
    for(int i = 0; i < 4; i++)
    {
        cout << arr[i] << "\t";
    }
    cout << endl;
    cout << endl;
    cout << endl;

    //--------------------------------------------示例2
    const int ci = 20;
    const int *pci = &ci;

    int *pci2 = const_cast<int*>(pci);
    *pci2 += 20;

    cout << ci << endl;   //20  它的值没有改变,有一种说法是:编译器编译的时候直接把 ci 当成宏替换成了20;是编译器做了工作。
    cout << *pci2 << endl;//40
    cout << *pci << endl; //40

    cout << "adress" << endl;  //地址都一样 编译器做了优化
    cout << &ci << endl;
    cout << pci2 << endl;
    cout << pci << endl;

3、static_cast

  • static_cast和C风格的强制转换的效果基本一样,由于没有运行时类型检查来保证转换的安全性,所以这类型的强制转换和C风格的强制转换都有风险。
  • 可以用于类层次结构中基类和派生类之间指针或引用的转换。注意:进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
/* class 的上下行转换 */
class Base{
    // something
};
class Sub:public Base{
    // something
}

//  上行 Sub -> Base
//编译通过,安全
Sub sub;
Base *base_ptr = static_cast<Base*>(&sub);  

//  下行 Base -> Sub
//编译通过,不安全
Base base;
Sub *sub_ptr = static_cast<Sub*>(&base);
  • 用于基本数据类型之间的转换,如把int转换成char、把int转换成enum。这种转换的安全性需要开发者自己维护。
int a = 10;
char p = static_cast<char>(a);	//注意溢出

float f_pi = 3.1415926f;
int i_pi = static_cast<int>(f_pi);  //i_pi 3 丢失精度,开发者需要自己维护安全性
cout << i_pi;

  • static_cast不能转换掉原有类型的const、volatile、或者__unaligned属性。(前面两种可以使用const_cast来去除)
  • 不支持不相关类型的转换
   int a = 10;
//    int *ptr = static_cast<int*>(a);    //error: cannot cast from type 'int' to pointer type 'int *'
  • c++ primer:c++的任何隐式转换都是使用static_cast实现的
  • 把空指针转换成目标类型的空指针。
  • 把任何类型的表达式转换成void类型。

4、reinterpret_cast

  • 用来处理两种无关数据类型转换的,通常为操作数的位模式提供较低层次的重新解释!
  • 但是他仅仅是重新解释了给出的对象的比特模型,并没有进行二进制的转换!
  • 他是用在任意的指针、引用之间的转换。
    B b;
//    A a = reinterpret_cast<A>(b); //编译通不过,b不知指针或者引用
    
    //下面是指针或引用的转化
    A *pa = reinterpret_cast<A*>(&b);
    A &ra = reinterpret_cast<A&>(b);
  • 或者是指针和足够大的int型之间的转换,反之,整数到指针的转换。
    int a = 20;
    void *p_a = reinterpret_cast<void*>(a);
    cout << reinterpret_cast<int>(p_a);     //20
    //or
    int *ptr = new int(233);
    uint32_t ptr_addr = reinterpret_cast<uint32_t>(ptr);
    cout << "ptr addr: " << hex << ptr << endl         //ptr addr: 0x10a6f98
         << "ptr_addr's value(hex): " << hex << ptr_addr << endl;	//ptr_addr's value(hex): 10a6f98

4、dynamic_cast

  • 其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。
  • 不能用于内置的基本数据类型的强制转换。
  • dynamic转换如果成功的话返回的是指向类的指针或引用,转换失败的话会返回nullptr。
  • 使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过。
    需要检测有虚函数的原因:类中存在虚函数,就说明它有想让基类指针或引用指向派生类对象的情况,此时转换才有意义。
    这是由于运行时类型检查需要运行时的类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表。
  • 在类的转换时,在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的。在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
  • 向上转换,即为子类指针指向父类指针(一般不会出问题);向下转换,即将父类指针转化子类指针。
  • 向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。
  • 在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。Dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。

三、为什么标准头文件都有类似以下的结构?

#ifndef TESTH_H
#define TESTH_H

#ifdef __cplusplus
extern "C"{
#endif

/*……*/

#ifdef __cplusplus
}
#endif

#endif // TESTH_H

1、防止重复包含

#ifndef TESTH_H
#define TESTH_H
/*……*/
#endif // TESTH_H

这几句是为了防止该头文件被重复include。

2、extern“C”

#ifdef __cplusplus
extern "C"{
#endif

/*……*/

#ifdef __cplusplus
}
#endif
  • 说明C++程序要调用C语言的函数,告诉编译器,用C语言的形式编译
  • C++支持函数重载,而C不支持,这也就导致了编译后的结果不用,例如:
    void foo(int x, int y)
    在C语言中编译后在符号表中为_foo;而在C++中编译后在符号表中为_foo_int_int,不仅包含函数名,还包括形参类型,用此来区分重载函数。
  • 在C/C++混合编程中,extern“C”有两种用法:
    ①在C++中定义函数编译成库给C程序来调用自己写的C++库,则会链接报错,需要在C++中的每个函数定义(函数实现的地方)前声明 extern"C",告诉编译器用C语言的形式编译。
    ②在C中定义函数,编译成库,在C++程序中调用,则会链接报错,在C 中每个函数的声明前面加extern “C”,为了方便,通常用上面的方式,集中声明。

四、分析下面代码有什么问题?

void test2()
{
 char string[10], str1[10];
 int i;
 for(i=0; i<10; i++)
 {
 str1  = 'a';
 }
strcpy( string, str1 );
}

纠正如下:

    char string[10];
    char str1[10];
    //    for(int i = 0; i < 10; i++)   //库函数strcopy进行拷贝操作,会从源地址一致往后拷贝,直到遇见'\0'为止。所以拷贝长度是不定的,如果一直没有遇到'\0'导致数组越界就会崩溃。
    //    {
            str1 = 'a';             //str1是const指针,只能做右值,不能赋值。
            *str1 = 'a';            //想给首元素赋值用该使用*解引用,*str1;
    //    }
    for(int i = 0; i < 9; i++)
    {
        str1[i] = 'a';
    }
    str1[9] = '\0';
    strcpy(string, str1);

五、给定一个数组,找出其中最小的K个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。如果K>数组的长度,那么返回一个空的数组。

思路

  • 改自快排的算法,众所周知快排就是递归的排序,将小于基准值的数放到左边,大于基准值的数放到右边。不停地对左右两边进行递归的排序。
  • 而这个算法题类似的是求最小的K个数字,则只需要求基准值索引为K时,左边的元素。
#include <iostream>
#include <list>
#include <vector>

using namespace std;

//三数取中
int GetMidIndex(int* a, int begin, int end)
{
    int mid = (begin + end) / 2;
    if (a[begin] < a[mid])
    {
        if (a[mid] < a[end])
            return mid;
        else if (a[begin]>a[end])
            return begin;
        else
            return end;
    }
    else    //a[begin]>a[mid ]
    {
        if (a[mid] > a[end])
            return mid;
        else if (a[begin] < a[end])
            return begin;
        else
            return end;
    }
}

//挖坑法
int PartSort(int *a, int begin, int end)
{
    int midIndex = GetMidIndex(a, begin, end);
    swap(a[midIndex], a[end]);

    int key = a[end];
    while (begin < end)
    {
        while (begin < end&&a[begin] <= key)
        {
            ++begin;
        }

        //左边找到比key大的,填到右边的坑
        a[end] = a[begin];

        while (begin < end&&a[end ]>= key)
        {
            --end;
        }

        //右边找到小的,填在左边的坑
        a[begin] = a[end];
    }
    a[begin] = key;

    return begin;
}
void QuickSort(int *a, int left, int right)
{
    //	assert(a);
    if (left < right)
    {
        int div = PartSort(a, left, right);
        //[left,div-1]和[div+1,right]
        QuickSort(a, left, div - 1);
        QuickSort(a, div + 1, right);
    }
}

void quickSort(char)
{

}

int partition(std::vector<int> &_vector, int start, int end)
{
    int tem = _vector.at(0);
    while (start < end) {
        while (start < end && _vector.at(end) >= tem) {
            end--;
        }
        *(_vector.begin() + start) = _vector.at(end);
        while (start < end && _vector.at(start) <= tem) {
            start++;
        }
        *(_vector.begin() + end) = _vector.at(start);
    }
    *(_vector.begin() + start) = tem;
    return start;
}
/*
 * 先进行(左 index 右)分隔
 *
 */
std::vector<int> solution(std::vector<int> &_vector, unsigned int k)
{
    std::vector<int> vector;
    if(_vector.empty() || k <= 0 || _vector.size() < k)
        return vector;
    int start = 0;
    int end = _vector.size() - 1;
    unsigned int index = partition(_vector, start, end);
    while (index != k - 1) {
        if(index > k - 1)
        {
            end = index - 1;
            index = partition(_vector, start, end);
        }
        else
        {
            start = index + 1;
            index = partition(_vector, start, end);
        }
    }
    for(unsigned int i = 0; i < k; i++)
    {
        vector.push_back(_vector.at(i));
    }
    return vector;
}


int main()
{
    cout << "Hello World!" << endl;

    int a[8] = {1, 6, 8, 3, 7, 6, 10, 5};
    QuickSort(a, 0, 7);
    for(int i = 0; i < 8; i++)
    {
        cout << a[i] << " ";
    }
    cout << endl;

    std::vector<int> vector;
    vector.push_back(4);
    vector.push_back(5);
    vector.push_back(1);
    vector.push_back(6);
    vector.push_back(2);
    vector.push_back(7);
    vector.push_back(3);
    vector.push_back(8);
    std::vector<int> vector_ = solution(vector, 4);
    for(auto it = vector.begin(); it != vector.end(); it++)
    {
        cout << *it << "";
    }
    cout << endl;
    return 0;
}

总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值