C/C++知识点

===

Index

基础语法

1. new, delete与malloc, free的区别联系

  • malloc, free是c语言库函数,new和delete是C++运算符
  • new与malloc都是堆分配。malloc仅分配一定size的字节,返回值是void*, new和delete会调用构造函数和析构函数,new 返回值是对象类型指针

2. 描述内存分配方式

  • 静态存储区分配: const, static,全局变量等
  • 栈上分配: 参数,临时变量等
  • 堆上分配:new操作

3. C与C++各自是如何定义常量的?有什么不同?

  • C中是使用宏#define定义, C++使用更好的const来定义。
  • 区别:
    • 1)const是有数据类型的常量,而宏常量没有,编译器可以对前者进行静态类型安全检查,对后者仅是字符替换,没有类型安全检查,而且在字符替换时可能会产生意料不到的错误(边际效应)。
    • 2)有些编译器可以对const常量进行调试, 不能对宏调试。

既然C++中有更好的const为什么还要使用宏?   const无法代替宏作为卫哨来防止文件的重复包含

4. Extern “C”的作用是什么?

实现C和C++混合编程 Extern “C”是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数。这是因为C++编译后库中函数名会变得很长,与C生成的不一致,造成C++不能直接调用C函数,加上extren “c”后,C++就能直接调用C函数了。

5. 指针和引用有什么区别?

  • 引用必须被初始化,指针不必。
  • 引用初始化以后不能被改变,指针可以改变所指的对象。
  • 不存在指向空值的引用,但是存在指向空值的指针。

6. 常量指针和指针常量

  • 指针常量: 我们把指针放到一个变量里面,就是指针变量;我们把指针放到常量中,就是指针常量;
  • 常量指针(常指针): 一个指针所指的数据是常量,这个指针被称为常量指针(指向常量的指针)。
  • 指针常量): 指针本身的内容是个常量,不可以改变
  • 常量指针/常指针: 指针所指向的内容不可以通过指针的间接引用(*p)来改变。

7. c++中四种cast转换

C++中四种类型转换是:static_cast, dynamic_cast, const_cast, reinterpret_cast

  • const_cast: 用于将const变量转为非const
  • static_cast: 用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;
  • dynamic_cast: 用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。
    • 向上转换:指的是子类向基类的转换
- 向下转换:指的是基类向子类的转换,它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。 >* reinterpret_cast: 几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

继承多态

1. 基类的析构函数如果不是虚函数,会带来什么样的问题?

  • 会发生只析构基类而不析构派生类的情况,造成内存泄露

2. 为什么要区分虚函数与普通函数?全用虚函数会有什么问题?

  • 虚函数会增加内存开销,当类里面有定义希函数的时候,编译器会给类添加一个虚函数表,表里存放虚函数指针,这样增加了存储空间,只有当一个类被用来作为基类时才把析构函数写成虚函数

3. 什么是纯虚函数?与虚函数有什么区别?虚函数的定义中能否使用static修饰符?

  • 纯虚函数仅有声明,没有实现,要到子类中实现,虚函数可在基类中实现;子类中不提供虚函数的实现,调用时会自动调用基类的实现;如果子类不提供纯虚函数的实现,编译会失败;
  • 不能用static修饰:static修饰的函数在编译时要求前期绑定,虚函数是动态绑定,而且被两者修饰的函数声明周期不同

4. 函数/虚表/虚指针是如何实现的?

  • 存在虚函数的类都有一个一维的虚函数表叫做虚表。每一个类的对象都有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。

5. 重载(overload)和重写(overwrite)有什么区别?

  • 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同).
  • 重写:是指子类重新定义父类虚函数的方法。
  • 重载是编译时多态,重写(+虚函数)是运行时多态。

6. 类的访问权限

  • public 成员:可以被任意实体访问
  • protected 成员:只允许被子类及本类的成员函数访问
  • private 成员:只允许被本类的成员函数、友元类或友元函数访问

7. 虚函数

  • 普通函数(非类成员函数)不能是虚函数
  • 静态函数(static)不能是虚函数
  • 构造函数不能是虚函数(因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针)
  • 内联函数不能是表现多态性时的虚函数

c++11特性

1.关键词

  • auto: 在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型, auto 变量必须在定义时初始化,这类似于const关键字,如果初始化表达式是引用或const,则去除引用或const语义。如果auto关键字带上&号,则不去除引用或const语意,初始化表达式为数组时,auto关键字推导类型为指针。若表达式为数组且auto带上&,则推导类型为数组类型。 C++14中,auto可以作为函数的返回值类型和参数类型
  • nullptr: C++11中引入保留字“nullptr”作为空指针,解决0带来的二义性问题
  • decltype: 利用已知类型声明新变量。有了auto,为什么还要整出一个decltype?原因是,我们有时候想要从表达式的类型推断出要定义的变量类型,但不想用该表达式的值初始化变量。decltype是在编译期推导一个表达式的类型,它只做静态分析,因此它不会导致已知类型表达式执行。decltype 主要用于泛型编程(模板)

2. 其他特性

  • 基于范围的for循环
  • 带初始化器的if和switch
  • 成员初始化列表
  • 智能指针:C++11新增了std::shared_ptr、std::weak_ptr等类型的智能指针,用于解决内存管理的问题。

3. c++11中的四个智能指针

四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被11弃用。

    1. auto_ptr(c++98的方案,cpp11已经抛弃)采用所有权模式。
    auto_ptr< string> p1 (new string ("I reigned lonely as a cloud."));
    auto_ptr<string> p2;
    p2 = p1; //auto_ptr不会报错.

** 2. unique_ptr(替换auto_ptr):unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。

采用所有权模式,还是上面那个例子

unique_ptr<string> p3 (new string ("auto"));   //#4
unique_ptr<string> p4                       //#5
p4 = p3;//此时会报错!!
    1. shared_ptr: shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。

    1. weak_ptr: weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

4. 为什么要使用智能指针:

  • 智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

如何定义一个只能在堆上(栈上)生成对象的类

1. 只能在栈上

  • 方法:将 new 和 delete 重载为私有
  • 原因:在堆上生成对象,使用 new 关键词操作,其过程分为两阶段:第一阶段,使用 new 在堆上寻找可用内存,分配给对象;第二阶段,调用构造函数生成对象。将 new 操作设置为私有,那么第一阶段就无法完成,就不能够在堆上生成对象。

1. 只能在堆上

  • 方法:将析构函数设置为私有
  • 原因:C++ 是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。

实现单例模式

单例模式:

  • 1.懒汉模式:第一次使用时才创建一个唯一的实例对象,从而实现延迟加载的效果。
  • 2.饿汉模式:程序启动时就创建一个唯一的实例对象。

1. 饿汉模式

class Singletion {
private:
    Singletion() {}

public:
    static Singletion * getInstance() {
        static Singletion instance;
        return &instance;
    }
};

2. 多线程下的懒汉模式

class Singleton {
private:
    static Singleton* m_instance;
    Singleton() {}

public:
    static Singleton* getInstance() {
        if (nullptr == m_instance) {
            // Lock();
            if (nullptr == m_instance) {
                m_instance = new Singleton;
            }
            // UnLock();
        }
        return m_instance;
    }
};

实现c语言字符串库函数

1. 实现strcmp函数

比较两个字符串字典序大小

int strcmp(const char* s1, const char* s2) {
    while (*s1 == *s2 && *s1 != '\0') {
        s1++; s2++;
    }
    return *s1 - *s2;
}

2. 实现strlen函数

int strlen(const char* s) {
    int idx = 0;
    while (s[idx] != '\0') idx++;
    return idx;
}

3. 实现 strcpy函数

  • 异常情况:** 源字符串和目标字符串不能重叠
  • 注意: 该函数第一个参数目的字符串,第二个参数是源字符串,
  • 把src的字符串拷贝到dst, restrict表明dst和src不能重叠,
  • 返回值为dst:能够让strcpy的结果能够再参与其它运算,投入到其他函数的参数
char* strcpy(char *dst, const char *src) {
    assert(dst != NULL && src != NULL);
    char *ret = dst;
    if (src <= dst && dst <= src + strlen(src)) {
        while (*src != '\0') {
            *src++; *dst++;
        }
        while (dst != ret) {
            *dst-- = *src--;
        }
        *dst = *src;
    } else {
        while ((*dst++ = *src++) != '\0') ;
        *dst = '\0';
    }
    return ret;
}

4. 实现memcpy

函数原型为 ` void *memcpy(void *restrict dst,const void *restrict src, size_t n)` 考虑空间重叠情况。

void *memcpy(void *dst, const void *src, size_t n) {
    int i;
    const char *s = (const char *)src;
    char *d = (char *)dst;
    assert(dst != NULL && src != NULL);

    for (int i = n - 1; i >= 0; --i) d[i] = s[i];
    return dst;
}

5. 实现strrev,反转一个字符串

char* strrev(char *s) {
    char *h = s, *t = s, ch;
    while(*t++) ;
    t--; t--;
    while (h < t) {
        ch = *h;
        *h++ = *t;
        *t-- = ch;
    }
    return s;
}

6. 实现strndup函数

char *strndup(char *src, int n) 复制字符串src,返回新的指针地址,最多复制n个字节(包括’\0’)

  • 是否检查参数的有效性,src, n
  • 是否能正确使用内存申请函数和指针
  • 对于最多复制n个字节的要求考虑
char* strdup(char* src, int size) {
    assert (src != NULL);
    char* des = (char)malloc(sizeof(char) * size); //动态分配内存
    for(int i = 0; i < size; i++){
        des[i] = src[i]; //逐字符拷贝
    }
    des[size] = '\0';
    return des; //返回字符备份指针
}

7. 实现strdup函数

char * strdup(char *str) {
   char * strNew;
   assert(str != NULL);
   strNew = (char *)malloc(strlen(str)+1);
   strcpy(strNew,str);
   return strNew;
}   

打赏一下

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦