C++ 基本知识整理
本基本知识整理及代码源于牛客网C++面试宝典导读,
网址https://www.nowcoder.com/tutorial/93/a34ed23d58b84da3a707c70371f59c21
Static关键字 (查询类变量内存所在位置)
1. 全局静态变量
内存中位置:静态存储区,且程序运行期间一直存在。
未经初始化的全局静态变量自动初始化为0。
全局静态变量在声明文件之外是不可见的。
2.局部静态变量
内存中位置:静态存储区。
未初始化自动初始化为0。
作用域为局部作用域,但离开作用域后不会销毁,仍然驻留在内存中,再次访问时值不变。
3.静态函数
函数定义声明默认为extern,但静态函数只在声明的文件中可见,其他文件不可见。
即使用static修饰则该函数只能在本文件中使用,且不会与其他文件中同名函数冲突。
全局函数应在头文件中声明,局部函数在cpp中声明带static
4.类静态成员
同一个类中静态成员在多个对象之间数据共享。
5.类静态函数
.在静态成员函数中不能直接引用类中的非静态成员,但可以引用类中的静态成员。静态成员函数中药引用非静态成员时要通过对象来引用
C++中内存分配
栈区:编译器自动分配和释放,存放函数参数值、局部变量等,操作方式类似栈。栈区最大值为1M,可以调整
堆区:手动申请的动态内存,分配方式类似于链表。
全局/静态区(数据段):已初始化的全局变量和静态变量。
BSS段:未初始化的全局变量和静态变量,以及被初始化为0的全局变量和静态变量
程序代码区(代码段):存放函数体二进制代码,只读存储区。
函数调用过程
首先参数从右向左压栈,将返回地址压栈,再将栈帧压栈。
C++如何处理返回值
生成一个临时变量,将其引用作为函数参数传入函数内。不能返回局部变量指针和引用。
strcpy和strlen
strcpy为字符串拷贝函数,原型:char *strcpy(char* dest, const char *src);
从src拷贝到dest,遇到‘\0’结束。可能导致越界,安全应用strncpy(char* dest, const char *src,n)
strlen计算字符串长度。到‘\0结束’
++i 和 i++实现
1.++i
int & int::operator++() { *this+=1; return *this; }
2.i++ (返回应是常量!)
const int int::operator++(int) { int ret=*this; *this++; return ret; }
指针和引用的区别
1. 指针有分配空间(大小是4个字节),引用没有(sizeof大小为引用对象的大小)
2. 指针初始化为NULL(nullptr),引用必须初始化为一个已有对象的引用。
3.参数传递时,指针需要解引用(*)才可以对对象操作,引用则可以直接修改。
4.指针在使用中可以改变指向的对象,但引用仅是别名,不能改变。
5.可以有多级指针,但引用只有一级(&&为右值引用)
new/delete与malloc/free的区别
new/delete是C++关键字,而malloc/fre是C语言库函数。
new是先分配内存,再构造对象,然后将对象绑定到内存的地址上。
delete是先析构对象,再释放内存。
malloc和free仅对内存操作,不使用构造/析构函数。
四个智能指针
四个智能指针为:shared_ptr,weak_ptr,unique_ptr,auto_ptr,前三个C++11支持
智能指针原理:智能指针为一个类,超出类作用域后,类会子懂调用析构函数,析构函数则会自动释放资源。
所以智能指针即在函数结束时自动释放内存空间,不需要手动释放。
1. shared_ptr
多个该智能指针可以指向相同对象,该对象和其相关资源在最后一个引用被销毁时释放。
通过use_count()查看资源所有者个数。可以通过new来构造,也可以传入其他智能指针构造。
调用release()时,可以手动释放资源所有权,且引用计数减一,当计数为0时资源被释放。
使用make_shared()或构造函数可以传入普通指针,或者通过get函数获取普通指针。
成员函数:
use_count() 返回引用计数
unique 返回是否独占(即引用计数为1)
reset() 放弃对象的所有权
2. unique_ptr(替换auto_ptr)
同一时间只有一个智能指针可以指向该对象。
不能将一个unique_ptr赋值给另一个,但如果源unique_ptr为一个右值则可以赋值。
3. weak_ptr
weak_ptr不控制对象生命周期,指向share_ptr管理的对象,仅是一种访问手段,但不会引起引用计数的增加或减少,是一种弱引用,不能访问对象的成员函数,需要通过lock()转化为shared_ptr才可以。
expired() 若use_count为0则返回true,否则返回false
lock() 返回对象的shared_ptr,不存在则返回空shared_ptr。
智能指针主要用于管理在堆上分配的内存,将普通指针封装成栈对象。当栈对象生存周期结束后,在析构函数中释放申请的内存,防止内存泄漏。
在两个shared_ptr成员变量指向对方时会造成循环引用,使引用计数失效,从而导致内存泄漏。为了解决这个问题,使用weak_ptr可以解决这个问题,weak_ptr不会修改引用计数,从而避免无法访问。
shared_ptr实现
主要实现引用计数,什么时候销毁底层指针,赋值和拷贝构造时引用计数变化。
参考:https://github.com/anbo225/shared_ptr/blob/master/sharedPtr.hpp
储存:底层真实指针,使用指针保存引用计数。
构造函数:
template<typename T> shared_ptr<T>::shared_ptr(T *p):ptr(p),use_count(new_int(1)) { }
拷贝构造函数
template<typename T> shared_ptr<T>::shared_ptr(const shared_ptr<T> &orig) { use_count=orig.use_count;//引用计数保存在一块内存 this->ptr=orig.ptr; ++(*use_count);//引用计数加1 }
重载=运算符
当赋值后,需要判断原对象的引用计数。template<typename T>shared_ptr<T>& shared_ptr<T>::operator=(const shared_ptr<T> &rhs)
{
if(&rhs != this){ //防止自赋值 ++(*rhs.use_count);//增加引用计数 if((--(*use_count))==0)//原引用计数为0则释放原内存 { delete ptr; ptr=nullptr; delete use_count; use_count=nullptr; } ptr=rhs.ptr; *use_count=*(rhs.use_count); return *this;
}
return *this;
}
析构函数
template<typename T> shared_ptr<T>::~shared_ptr() { if(ptr && --(*use_count)==0) //ptr存在 { delete ptr; ptr=nullptr; delete use_count; use_count=nullptr; } }
函数指针
指向一个具体函数的指针变量。每一个函数都有一个入口地址,该入口地址就是函数指针指向的地址,可以使用该指针变量调用函数。
fork()函数
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
调用fork()会创建一个新进程。在子进程中fork调用返回的是0,父进程返回子进程的pid,如果错误则返回负值。
在调用fork()后,可以使用exec()载入二进制映像来替换当前进程的映像。
在linux中对进程内部结构采用写时复制的方法,而不是将父进程整体复制。(需要查阅)
写个函数在main函数前执行的
在gcc下使用attribute声明多个constructor、destructor
__attribute((constructor))void before() { printf("before main\n"); }
C++中析构函数作用
当对象结束生命周期时,系统自动执行析构函数。一个类只能有一个析构函数,不能重载。
如果不编写析构函数,则无法回收动态分配的内存。
析构顺序 类本身的析构函数->对象成员的析构函数->基类析构函数
静态函数和虚函数区别
静态函数在编译时就已经确定运行时机,而虚函数则在运行时动态绑定。
虚函数使用虚函数表机制,调用时会增加一次内存开销
重载和覆盖(重写)和隐藏
重载:两个函数名相同,但参数列表不同,在同一个作用域内。
重写:子类继承父类,父类中函数时虚函数,在子类中重新定义了这个函数。
C++调用C函数需要extern C,因为C语言没有重载。
隐藏:1.子类函数与父类函数的函数名相同,参数不同,不管父类是不是虚函数,都隐藏。
2.子类函数与父类函数名和参数都相同,但父类不是虚函数。
虚继承
用在多重继承中,防止继承一个类两次:例如,类A派生类B,类C,D同时继承B和C,此时就需要使用虚继承防止继承A两次。
虚函数和多态
多态分为静态多态和动态多态。
静态多态指函数重载,在编译时确定。
动态多态指虚表指针指向的虚表中的虚函数不同,运行时表现出来。
虚函数的实现:有虚函数的类中,类最开始的部分是一个虚函数表的指针,指针指向一个虚函数表,表中存放虚函数的地址,当子类继承父类时也会继承续函数表。当子类重写父类中虚函数时,会将其继承到的虚函数表中的地址替换为重写的函数地址。使用虚函数会增加访问内存开销
为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数
析构函数必须是虚函数,为了保证创建一个子类,父类指针指向该对象时,释放基类指针可以释放掉子类的空间,防止内存泄漏。
只有要当做父类时虚函数才需要,而虚函数则需要生成额外的虚函数表及虚指针占用额外内存,对于不会被继承的类来说则会浪费内存。
C++中拷贝构造函数和拷贝赋值函数不能进行值传递
在调用时首先将实参传递给形参,传递过程需要调用拷贝构造函数,如此会循环爆栈。