C/C++相关知识点

1. 各种数据类型的范围

#include<climits>

cout<<"int max:"<<INT_MAX;

2. 更合适的用于遍历数组的数据类型

#include<cstddef>

for(size_t i=0;i<N;i++)

3. 引用的本质是指针常量,指向不能变,值可变

4. malloc和alloc

malloc在堆上申请空间,需要用free手动释放内存;

alloc在栈上申请空间,由操作系统负责释放;

5. c++和c中的结构体

C:在定义结构体对象时,要加上struct;

C++:在定义结构体对象时,不用加struct;

6. define和const的区别

define是单纯的文本替换,没有安全校验,而const有安全检查;

define在预处理阶段起作用,const在编译、运行阶段起作用;

宏定义只是文本替换,不占内存空间,const修饰的变量要给他分配内存空间。

7. static

非类:只在本文件内可用,对其他文件隐藏;

          初始化为0;

          在函数内定义,始终存在,且只进行一次初始化,作用域和局部变量一样。

类内:成员变量只与类关联,所有对象公用一个变量,在类外初始化;

           成员函数不具有this指针,不能使用类的非静态成员,依赖于类存在。

8. 对象所占空间大小

所有非静态成员变量 +(虚函数指针)+ 对齐所用占位;

空类的对象size是1,当作为基类时,大小是0.

9. 拷贝构造和赋值

strint str1="nihao";

拷贝构造:string str = str1;

赋值:string str; str = str1;

10. 未申请内存的对象指针调用成员函数

A* a = NULL;

a->func();

如果func函数内用到了this指针,在运行时会报错;如果没用到this指针,正常执行。

11. CRTP模板

template <typename Derived>  
class Base {  
public:  
    void foo() {  
        static_cast<Derived*>(this)->bar(); // 使用派生类的类型  
    }  
};  
  
class Derived : public Base<Derived> {  
public:  
    void bar() { // 在基类中调用派生类的成员函数  
        // 执行派生类的特定代码  
    }  
};

这种情况父类的析构函数不应该声明成虚函数,通过模板来实现运行时多态,不需要用到虚函数表。

12. 构造函数不能是虚函数

虚函数需要通过虚函数表调用,在构造函数执行的时候,对象还没有创建,自然没有虚函数表;所以构造函数不能是虚函数。

13. inline内联函数和define宏定义

内联函数在编译期展开,宏定义在预处理时期替换;

内联函数由安全检查,宏没有;

内联函数是个函数,具有函数的特性,比如重载,宏没有。

14. register和volatile

register关键字告诉编译器这个变量要尽可能的放在寄存器,以提高访问速度;

volatile关键字告诉编译器这个变量可能会被意外修改,每次都要从内存中去读;

inline和register都是建议型关键字,即编译器不一定按他说的来。

15. inline修饰构造、析构、虚函数

        构造和析构声明成内联函数是没有意义的,因为在class内声明和定义的函数默认都是内联函数,但是编译器不一定会对他内联,还要具体情况具体分析。

虚函数声明成内联函数有两种情况:

        指向派生类的指针调用inline虚函数,不会内联展开(一个编译器,一个运行期);

        对象本身调用inline虚函数时,可以内联展开(前提是函数并不复杂)。

16. auto,decltype和decltype(auto)

auto:根据初始值自动判断变量类型

        auto a = 1;//int

decltype:根据表达式的返回值判断变量类型

        decltype(func()) b = 2;//func的返回值类型

decltype(auto):单纯的auto去推导变量类型可能会发生隐式类型转换,如下代码;传递的是vector,而不是vec[0],但用auto推导的话,会隐式转换成int类型,而decltype(auto)会匹配原有类型。

void foo(int x) {  
    auto y = x;  // y 的类型是 int  
    decltype(auto) z = x;  // z 的类型是 vector 
  
    std::cout << "y: " << y << ", z: " << z << std::endl;  
}
main() {
    std::vector<int>vec;
    foo(vec);
}

17. 对象做返回值

linux环境下,不管是值返回还是引用返回,都不会发生拷贝构造;

windows + vs环境下,如果是值返回,会发生拷贝构造。

18. plain 、nothrow 、placement new

plain new:默认的new(int* a = new int;),如果申请内存失败,会抛出一个bad_alloc的异常,用返回值是否等于NULL是不能判断申请成功与否的;

nothrow new:(int* a = new(nothrow) int;),如果空间分配失败,会返回NULL;

placement new:不申请内存,在一块已经申请成功的内存上重新构造对象或对象数组,他只做一件事:调用构造函数;在释放的时候,不能使用delete,要显式的调用析构函数。

#include <iostream> 
using namespace std;
  
class MyClass {  
public:  
    MyClass() { /* Constructor code here */ }  
    ~MyClass() { /* Destructor code here */ }  
    // Other member functions and variables here  
};  
  
char* buffer = new(nothrow) char[sizeof(MyClass)+1] // Allocate memory for 10 objects  
  
MyClass* pObject = new(buffer) MyClass;  // Construct object in preallocated memory  

pObject->~MyClass();  // Call destructor manually to destruct the object  

delete buffer;  // Deallocate memory

19. thread_local

        该关键字和static关键字组合,可以让被修饰的变量在每个线程里都初始化一次,即单个线程内部共享,但多个线程之间不共享。

20. static和virtual

        静态成员函数不能声明为虚函数,因为虚函数是通过this指针找到他的虚表指针,进而调用虚函数;但是静态成员函数是依赖于类存在的,没有this指针。

21. static变量的初始化

        在C语言里,静态变量(全局和局部)是在编译阶段分配好内存后就进行初始化,所以不能用变量去给静态变量初始化;

        在C++里面,静态变量(全局和局部)是在执行相关代码的时候才会初始化,所以可以用变量来给静态变量初始化。

22. C++模板

        编译器会对函数模板进行两次编译,一次是在声明的地方对模板本身进行编译,一次是在调用的地方队参数替换后的代码进行编译。模板只有在实例化之后才是真正的函数。

23. delete怎么知道释放内存的大小的

        对于数组,在new的时候会额外分配四个字节大小的空间,用来存放数组的大小,在delete的时候,直接取到这个大小进行释放就好了。

24. malloc实现原理

        当申请小于128K的空间时,使用brk()来申请内存,将堆的最高地址指针想高地址推;

        当申请大于128K的空间时,使用mmap()来申请内存,实在堆和栈之间的文件映射区找到一块内存来分配;

        申请到的空间会放在空闲链表,当程序要用到的时候,就去空闲链表中找到第一个符合要求的空间分配过来。

25. 赋值初始化和初始化列表初始化

        赋值初始化,即在函数中初始化,是在所有数据成员都分配好空间之后赋值的;

        初始化列表初始化是在分配内存的同时进行初始化;

        后者效率高些。

26. 内存泄漏检测方式

linux工具:valgrind

sudo apt-get install valgrind
gcc -g -o myprogram myprogram.c
valgrind --tool=memcheck myprogram
valgrind --tool=memcheck --leak-check=full myprogram

27. 函数的存放位置

        函数都是存放在代码区的,不管是普通函数、成员函数还是静态函数。

28. 构造函数和析构函数内部调用虚函数

        在构造、析构函数内部调用虚函数是不采用动态联编的,执行的就是本类中的方法。

29. 构造、析构抛出异常 

        在构造函数中抛出异常可能导致对象尚未完全初始化,这可能会导致问题。同样,在析构函数中抛出异常可能导致资源释放失败或不完整,这可能会导致内存泄漏或其他问题。 

30. 智能指针

        智能指针的本质是一个类,用来存储一个指向动态分配对象的指针和一个引用计数;动态分配的资源,交给一个对象去管理,当类对象的生命周期结束时,自动调用析构函数释放资源。

(shared_ptr的引用计数是线程安全的,但是对象的读取需要加锁)

31. 强制转换

reinterpret_cast:指针之间的类型转换

const_cast:去常加常转换

static_cast:基本数据类型之间的转换、父子类的抓换,但没有类型检查。

dynamic_cast:父子类的转换,有类型检查,如果下行转换是不安全的会返回空。

32. 临时变量做返回值

        临时变量的生命周期在函数内部,函数返回后,生命周期就结束了,不能在函数外部使用这个变量,但是该变量的值会保存在寄存器中,所以如果想承接返回值的话,直接以复制的方式保留下来就好了。

(C语言规定:16bit程序中,返回值保存在ax寄存器;32bit程序,保存在eax寄存器;64bit程序,低32bit保存在eax寄存器,高32bit保存在edx寄存器)

33. 浮点数比较

        浮点数之间是不能用“==”去进行比较的,结果可能和现实由出入,如果要比较的话,可以通过想减再与某个精度进行比较。

34. 类的静态和动态分配

让类只能静态分配:把new、delete运算符重载成private属性;

让类只能动态分配:把构造、析构设成私有,再用子类来动态创建。

35. 打印字符串到屏幕的过程

1. 执行程序(./xx);

2. 找到程序的相关信息,检查其类型是否为可执行文件,根据首部信息,确定代码和数据在可执行文件中的位置并计算出对应的磁盘块地址;

3. 操作系统创建一个进程,让该进程去执行这个可执行程序;

4. 为程序设置cpu上下文环境,并跳到程序开始处;

5. 执行指令,会发生缺页异常;

6. 操作系统分配一页物理内存,将代码从磁盘读入内存,继续执行程序;

7. 程序执行puts函数(系统调用),在显示器上写字符串;

8. 操作系统找到显示设备,操作系统将字符串发送给控制该显示设备的进程;

9. 进程告诉窗口,要显示字符串,窗口将字符串转换成像素,写入到设备的存储映像区;

10. 视频硬件将像素转换成显示器可接受的一组控制数据信号;

11. 显示器解释信号,激发液晶屏,显示出来。

36. 函数模板和类模板

        函数模板可以显示调用也可以隐式调用,类模板必须显示调用(<>)

37. ++ite和ite++

        ++ite会返回的是引用,ite++返回的是对象。后者会产生临时对象,导致效率降低。

38. 模板函数的声明和定义要写在一个文件里

        如果声明和定义不在一个文件,在编译期的时候,编译器找得到模板类或函数的声明而找不到实现,就创建一个符号,希望链接程序找到实现的地址;但是模板类或函数在没被实例化的时候不会被编译成二进制代码,链接器找不到地址就会报错。

39. 成员函数调用delete this

        普通成员函数调用:调用delete this会回收对象空间,在这条语句后面如果使用到对象空间的内容了(成员函数,成员变量),会发生不可预期的错误;

        析构函数调用:由于delete this语句就回去调用对象的析构函数,会形成无限递归,造成堆栈溢出,系统崩溃。    

40. 指针和引用做参数的使用时机

        指针:内置数据类型、数组、结构体

        引用:结构体、类对象

41. 函数参数的入栈顺序是从右向左

42. 禁止程序自动生成拷贝构造的方法

        基类将拷贝构造函数和拷贝赋值构造函数设置成private,这样派生类中编译器将不会自动生成这两个函数。

43. memset(this,0,sizeof(*this))

        可以用该语句在构造函数中给类内成员初始化,但是要注意两点:

1. 类中不能有虚函数,这样会破坏虚函数表;

2. 不能有父类,会破坏父类空间。

44. c模拟继承和多态

typedef void (*fun)();//函数指针

struct FA{
    fun m_fun;//模拟成员函数
    int m_a;
};

struct SON{
    FA m_fa;//模拟继承
    int m_b;
};

void fun_a(){}
void fun_B(){}

int main(){
    FA ca;
    SON cb;
    ca.m_fun = fun_a;
    cb.m_fa.m_fun = fun_b;

    FA* pa = (SON*)&ca;
    pa->m_fun();//模拟多态
}

45. STL哈希表是用拉链法解决冲突的

46. 在STL容器中删除元素

        序列容器:ite = c.erase(ite);

        关联容器:c.erase(ite++);

47. map的插入方式

(1) ma.insert(pair<int,int>(1,2));

(2) ma.insert(map<int,int>::value_type(1,2));

(3) ma.insert(make_pair(1,2));

(4) ma[1] = 2;

48. unordered_map和map

使用时,key如果是自定义类型,map的key需要定义operator<,unordered_map的key需要定义hash_value()函数并重载operator==

49. STL中的下标越界

vector中使用下标访问元素,没有越界检查,即使越界,也不会报错,而是返回对应的地址中的值。如果想有越界检查,可以用函数

        vec.at(2);//vec[2]

50. vector

        vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,再插入新增的元素;(VS中以1.5倍扩容,GCC以2倍扩容)

        对vector的任何操作,只要引起了空间的重新分配,指向原vector的所有迭代器就失效了。

        和数组的区别:数组在空间不足时,需要手动定义一块跟大的空间,将数据移动到新空间,释放旧空间;而vector这些步骤都由系统自动完成了。

----------------------------

51. 占位符

using namespace std::placeholders;

fun(_1,_2);

52.字符串和各种类型的转换

1. 字符串转整形、浮点型

atoi(str);

atof(str);

2. 字符串转字符数组

str.c_str();

3. 整形、浮点型转字符串

std::stringstream ss;

ss << i;

ss.str();

4. 字符串转十六进制

const char* hex = "0123456789ABCDEF";
std::stringstream ss;
for (auto ch : str)
	ss << hex[(unsigned char)ch >> 4] << hex[(unsigned char)ch & 0xF];
ss.str();

53. 管理锁

lock_guard提供自动化的互斥锁管理,确保在离开作用域时释放互斥锁。

std::mutex mtx;//定义锁
std::lock_guard<std::mutex> lock(mtx);//上锁
//超出作用域自动释放锁

54. 线程相关

using namespace std::chrono;
std::thread(func,1);
std::this_thread.sleep_for(100ms);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值