1、变量作用域
变量根据作用域的不同可分为局部变量和全局变量。全局变量的作用域是本工程(但要访问其他文件中的全局变量需要使用extern关键字),生命周期是从声明到整个工程运行结束,因此存放在内存四区(代码区、全局区、堆区、栈区)的全局区,在工程结束时被销毁;局部变量作用域是本代码块(以{}分隔),生命周期是从声明到本代码块运行结束,存放在栈区,随着本代码块的运行结束会被自动销毁。
特殊情况:
(1)如在某代码段声明一个与全局变量同名的局部变量,在局部变量的生命周期内全局变量将被局部变量屏蔽。
(2)一个全局变量只能在同一工程中的一个源文件中被定义,其他源文件中不能重复定义同名全局变量,若想访问其他文件中的全局变量,可以使用extern关键字。
source2.cpp中定义全局变量:
source1.cpp中使用extern关键字访问:
d在source1.cpp声明时不可再赋值:
(3)若多个源文件都需要访问同一个全局变量,可以在一个源文件中实现,在一个头文件中使用extern进行声明,再在其他源文件中include此头文件。
若在头文件中直接定义全局变量,也可以通过include的方式被包含在源文件中,此时相当于该全局变量在该源文件中被定义,其他源文件不可再定义,可通过extern访问,而不能通过include头文件访问,否则相当于在多个源文件中重定义相同的全局变量。
2、static
static是一个起修饰作用的关键字,可以修饰变量、函数以及类等等。
(1)static修饰变量
static关键字能够改变变量的生命周期和作用域。
当使用static修饰局部变量,称为静态局部变量,它将被存放在全局区,即在程序结束前都不会被销毁。但需要注意,被延长的只有生命周期,作用域并没有被改变,仍然是本代码段。
静态全局变量在第一次声明时被创建,后面每次操作都是在原来所有操作的结果上继续叠加,但后续的声明将被忽略。
当使用static修饰全局变量,称为静态全局变量,该变量的作用域将被固定为本文件,不能再被其他文件访问(若使用extern访问,编译不报错,链接报错)。如果多个文件存在同名静态全局变量,将互不影响。这样做的好处是降低耦合,减少误用,增加安全性。
头文件head.h:
source2.cpp:
main.cpp:
(2)static修饰普通函数
当使用static修饰函数,情况与全局变量类似,都是将作用域限定在本源文件内,拒绝其他文件访问。
错误场景1:在source.cpp中定义一个static修饰的函数,在头文件head.h的声明中也使用static修饰,在main.cpp中包含head.h,但未对该函数进行实现,直接进行调用。链接时报错:静态函数已声明未定义。说明编译器把声明当作内部函数(本文件的函数),在本文件未找到定义,因此报错。
错误场景2:在source.cpp中定义一个static修饰的函数,在头文件head.h的声明中未使用static修饰,在main.cpp中包含head.h,但未对该函数进行实现,直接进行调用。链接时报错:无法解析的外部符号。说明编译器把声明当作外部函数,链接时未找到该函数,因此说不认识该函数。
(3)static修饰类的成员
类中的成员包括成员变量和成员函数,一个类无论被实例化出多少对象,其中被static修饰的成员只会有一份拷贝,其内容被所有对象共享,不占用对象内存,可使用类名直接调用(需要成员的权限是public)。
静态成员变量类内声明,类外定义+初始化。相比于全局变量,静态成员变量的一大优势是可以被设置为private权限,另一个优势是其只在某个类的名字空间下,减少冲突。
静态成员函数只能访问静态成员变量和静态成员函数,不能访问普通成员;普通成员函数可访问静态成员、也可以访问非静态成员。
其原因在于每个对象在实例化时都隐含着一个this指针,这个指针指向对象本身,普通成员确定得属于某一个对象,因此可以通过该对象的this指针访问,而静态成员属于整个类,因此没有this指针,也就无法访问普通成员。
同样因为指针的原因,静态成员函数不能是虚函数。因为虚函数通过虚函数表调用,指向虚函数表的指针存储在对象的内存中,静态成员函数不属于任何对象,因此无法通过虚函数表指针访问。
3、存储类型
C语言中提供了存储说明符auto、register、extern、static说明的四种存储类别。四种存储类别说明符有两种存储期:自动存储期和静态存储期。其中auto和register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时撤销。
在C++11中,auto被定义为自动推断变量的类型,如:
map<int,int>m;
for(auto it=m.begin();//这里it被auto推断为map<int,int>::iterator类型
it!=m.end();++it)
{
//....
}
需注意,在C++11中使用auto关键字时有一个限定条件,那就是必须给声申明的变量赋予一个初始值,否则编译器在编译阶段将会报错。
4、多态
多态分为静态多态和动态多态。
1、静态多态
实现方式是函数重载。重载指的是不同函数使用同一个函数名,不同的参数列表进行区分。返回值不能作为区分条件。此时多态是在编译期间就确定的,因此称为静态多态。
2、动态多态
实现方式是函数重写+虚函数。重写发生在有继承行为存在的场景。子类中可以对父类函数进行重写,当子类对象调用该函数时,执行子类中实现的具体内容。重写的条件是函数名、参数列表和返回值都必须相同,即相同的壳,不同的内容。此时多态是在运行期间才确定,因此称为动态多态。
5、虚函数
当类的声明里存在虚函数,编译器就会为该类的每个对象隐式地增加一个指针,这个指针指向一个数组,数组里存放这个类所有虚函数的地址,这个数组称为虚函数表,指向这个表的指针称为虚函数表指针。
在存在继承行为的情况中,基类的所有成员都会被子类继承(但private权限的成员对子类不可见),子类另外有自己的成员。如果基类中存在虚函数,子类的对象也会拥有虚函数表指针。如果子类中未重写基类中的虚函数,则子类的虚函数表指针指向的虚函数表里地址与基类虚函数表里的地址相同;如果子类中重写了基类中的虚函数,则子类的虚函数表指针指向的虚函数表里地址被替换成子类自己的虚函数地址。
https://blog.csdn.net/qq_25427995/article/details/126690153
虚函数表(VFT)本身位于只读数据段(.rodata),即常量区(全局区)。
虚函数代码位于代码段(.text),即代码区。
虚函数表指针(vptr)的位置与对象存储的位置相同,可能位于栈、堆或数据段等。
using namespace std;
class Base {
virtual void func()
{
cout << "base::func" << endl;
}
};
class Drive : Base {
void func()
{
cout << "Drive::func" << endl;
}
};
int main() {
Base *base = new Base();
Drive *drive = new Drive();
long* pBase = (long*) * ((long*)base);
long* pDrive = (long*)*((long*)drive);
cout << hex << pBase << endl;
cout << hex << pDrive << endl;
system("pause");
return 0;
}
子类和父类的虚函数表地址不同。
如果子类未重写父类中的虚函数:
子类仍然有自己单独的虚函数表,只是表里函数的地址与父类相同。
5、define
#define 定义一个预处理宏
#undef 取消宏的定义
#if 编译预处理中的条件命令,相当于C语法中的if语句
#ifdef 判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef 与#ifdef相反,判断某个宏是否未被定义
#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if(扩展条件)
#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else(扩展条件)
#endif #if, #ifdef, #ifndef这些条件命令的结束标志.
defined 与#if, #elif配合使用,判断某个宏是否被定义
6、const
被const关键字修饰的变量或对象内容不可更改。
特殊说明:
1、该块内存可以通过指针修改。
const int Max=100;
int *p = &Max;
*p = 101; //允许修改,但出警告
2、早期版本的C编译器不允许变量做数组长度,加const也不行,因为本质还是变量,但是高版本C编译器及C++允许变量做数组长度。
3、const只修饰其后的变量,与类型符的前后顺序并没有关系,如const int a 与 int const a 意义是相同的。
4、星号应与变量名结合,而不是与类型符结合。如const int *a 实际意义是const int (a),代表这是一个常量指针,意思是它本身是一个指针,指向一个int类型的常量,也就是不可以通过这个指针去改变它指向的地址里存储的值,但该指针可以指向其他地址。而int * const a实际意义是int ( const a),代表这是一个指针常量,意思是它本身是一个常量,数据类型是指向int型变量的指针,该指针的内容也就是指向的地址不能变,但是地址中存储的内容可变。
5、int const * const p1,p2; p1和p2都被第一个const修饰,只有p1被第二个const和星号修饰。
7、智能指针
智能指针本质是一个封装了一个原始C++指针的类模板,将一块动态内存托管给一个对象,在对象析构时保证内存释放。
https://zhuanlan.zhihu.com/p/642134340?utm_id=0
8、匿名函数(lambda表达式)
匿名函数本质上是一个对象,在其定义的过程中会创建出一个栈对象,内部通过重载()符号实现函数调用的外表。
https://blog.csdn.net/Long_xu/article/details/127869979
9、左值与右值
计算机数据放在内存,内存有两个很基本的属性:内存地址和内存里面放的数据。
变量名编译之后,会映射成内存地址,例如a = b的含义其实就是将b地址内存里面的数据,放到a地址内存中。
左值本质可以看作是内存地址,可以通过这个地址去访问一块内存,进行读写操作;右值可以看作是某块内存里的数据,只能读取。
左值引用:给一个左值起别名,即多个名字映射到同一个地址,本质还是左值。意义:避免不必要的拷贝行为。
右值引用:给右值起一个别名,用于解决左值引用的短板——函数返回值为临时变量时无法用左值引用将返回值传出。
https://blog.csdn.net/weixin_39318565/article/details/131231149
10、进程和线程
https://www.sohu.com/a/498391174_488672
进程间通信方式:管道、共享内存、消息队列、信号量
线程间通信方式:互斥量、条件变量、信号量、原子操作
线程安全:一段代码如论多少线程何时调用,返回的结果都是正确的。
每个线程都有一个私有的栈区,因此在栈上分配的局部变量就是线程私有的,无论如何调用都不会影响其他线程。
死锁:线程死锁的原因通常是由于以下四个条件同时满足:
互斥条件。一个资源每次只能被一个线程使用。12
请求与保持条件。一个线程在申请资源的同时保持对已有资源的占有。12
不剥夺条件。线程已获得的资源在未使用完之前,不能被其他线程强行剥夺,只能由该线程自己释放。
循环等待条件。若干线程之间形成一种头尾相接的循环等待资源关系
构造函数、析构函数
sizeof
11、pragma once 和 #ifndef
https://blog.csdn.net/qq_21438461/article/details/130874972
12、静态库和动态库
https://blog.csdn.net/qq_52154068/article/details/128572505
杂散知识点:
1、C/C++不允许在函数外进行赋值操作(以及其他执行语句,但可初始化),因为函数外语句的执行顺序是无法确定的。