保持变量内容持久: static 作用于局部变量,改变了局部变量的生存周期,使得该变量存在于定义后直到程序运行结束的这段时间。
隐藏: static 作用于全局变量和函数,改变了全局变量和函数的作用域,使得全局变量和函数只能在定义它的文件中使用,在源文件中不具有全局可见性。(注:普通全局变量和函数具有全局可见性,即其他的源文件也可以使用。)
静态成员函数和非静态成员函数的根本区别在于有无this指针。 非静态函数由 对象名. 或者 对象指针->调用,调用时编译器会向函数传递this指针;静态成员函数则有类名::或者对象名.调用,没有this指针,不识别对象个体,经常用来操作类的静态数据成员。
类的静态成员(数据成员和函数成员)为类本身所有,在类加载的时候就会分配内存,可以通过类名直接访问,无须创建任何对象或实例就可以访问;非静态成员(数据成员和函数成员)属于类的实例(对象)所有,所以只有在创建类的实例的时候才会分配内存,并通过实例去访问。静态函数只有当程序结束的时候才从内存消失。非静态则是动态加载到内存,不需要的时候就从内存消失。
静态成员函数不能用virtual修饰的原因:
虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable。对于静态成员函数,它没有this指针,所以无法访问vptr。因此,static函数不能为virtual。虚函数的调用关系:this -> vptr -> vtable ->virtual function。
静态成员函数不能用const、volatile修饰的原因:
当声明一个非静态成员函数为const时,对this指针会有影响。对于一个Test类中的const修饰的成员函数,this指针相当于Test const , 而对于非const成员函数,this指针相当于Test。而static成员函数没有this指针,所以使用const来修饰static成员函数没有任何意义。volatile的道理也是如此。
静态函数中不能使用非静态变量,也无法访问非静态成员函数,只能调用其他的静态成员函数。静态方法不可以定义this, super关键字(因为静态比对象先加载,而this在创建对象时加载,所以导致冲突,不能使用);非静态函数可以访问静态变量。
static函数的使用场景
普通静态函数只在同一文件中调用,只能在声明它的文件当中可见,与其它文件中定义的同名函数不会冲突。
静态成员函数,为类的全部服务而不是为某一个类的具体对象服务。
成员变量:是在类中声明的,依类而生,离开类之后就不是成员变量。成员变量只能通过对象访问。存储在栈中。静态成员变量类内声明,类外初始化。
new 是 C++ 中的关键字,用来动态分配内存空间,实现方式如下:
int *p = new int[5];
内存泄漏
指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况(如只new不delete)。它并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
检测可行的办法:在申请内存时记录下该内存的地址和在代码中申请内存的位置,在内存销毁时删除该地址对应的记录,程序最后统计下还有哪条记录没有被删除,如果还有没被删除的记录就代表有内存泄漏。
malloc的原理:
malloc的底层实现:
brk()函数实现原理: 向高地址的方向移动指向数据段的高地址的指针 _enddata。
mmap内存映射原理:
malloc函数线程安全但是不可重入的。
在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。一个函数要做到线程安全,需要解决多个线程调用函数时访问共享资源的冲突。
可重入:在运行某函数或代码时因为某个原因(中断或者抢占资源问题)而中止函数或代码的运行,等到问题解决后,重新进入该函数或者代码继续运行。其结果不会受到影响(和没有被打断时,运行结果一样)。要做到可重入,需要不在函数内部使用静态或全局数据,不返回静态或全局数据,也不调用不可重入函数。
malloc函数在用户空间要自己管理各进程共享的内存链表,由于有共享资源访问,本身会造成线程不安全。为了做到线程安全,需要加锁进行保护。同时这个锁必须是递归锁,因为如果当程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数,如果使用一般的锁就会造成死锁(信号处理函数中断了原程序的执行),所以要使用递归锁。
虽然使用递归锁能够保证malloc函数的线程安全性,但是不能保证它的可重入性。按上面的场景,程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数就可能破坏共享的内存链表等资源,因而是不可重入的。
至于malloc函数访问内核的共享数据结构可以正常的加锁保护,因为一个进程程调用malloc函数进入内核时,必须等到返回用户空间前夕才能执行信号处理函数,这时内核数据结构已经访问完成,内核锁已释放,所以不会有问题。
void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针(NULL)。
char *Ptr = NULL;
Ptr = (char *)malloc(100 * sizeof(char));
void free(void *FirstByte):该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。
free(Ptr);
Ptr = NULL;
在使用的时候 new、delete 搭配使用,malloc、free 搭配使用。 malloc、free 是库函数,而new、delete 是关键字。
首先执行该对象所属类的析构函数;
进而通过调用 operator delete 的标准库函数来释放所占的内存空间。
delete 用来释放单个对象所占的空间,只会调用一次析构函数;
delete [] 用来释放数组空间,会对数组中的每个成员都调用一次析构函数。
const 成员变量:
const 成员函数:
C++ 是在 C 语言的基础上发展起来的,为了与 C 语言兼容,C++ 中保留了 struct。
struct A{};
class B : A{}; // private 继承
struct C : B{}; // public 继承
union 是联合体,struct 是结构体。
typedef union
{
char c[10];
char cc1; // char 1 字节,按该类型的倍数分配大小
} u11;
typedef union
{
char c[10];
int i; // int 4 字节,按该类型的倍数分配大小
} u22;
typedef union
{
char c[10];
double d; // double 8 字节,按该类型的倍数分配大小
} u33;
typedef struct s1
{
char c; // 1 字节
double d; // 1(char)+ 7(内存对齐)+ 8(double)= 16 字节
} s11;
typedef struct s2
{
char c; // 1 字节
char cc; // 1(char)+ 1(char)= 2 字节
double d; // 2 + 6(内存对齐)+ 8(double)= 16 字节
} s22;
typedef struct s3
{
char c; // 1 字节
double d; // 1(char)+ 7(内存对齐)+ 8(double)= 16 字节
char cc; // 16 + 1(char)+ 7(内存对齐)= 24 字节
} s33;
int main()
{
cout << sizeof(u11) << endl; // 10
cout << sizeof(u22) << endl; // 12
cout << sizeof(u33) << endl; // 16
cout << sizeof(s11) << endl; // 16
cout << sizeof(s22) << endl; // 16
cout << sizeof(s33) << endl; // 24
cout << sizeof(int) << endl; // 4
cout << sizeof(double) << endl; // 8
return 0;
}
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
volatile 的作用:当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为 volatile,告知编译器不应对这样的对象进行优化。
volatile不具有原子性。(实现原子操作用automic,版本:C++11及以上)
volatile 对编译器的影响:使用该关键字后,编译器不会对相应的对象进行优化,即不会将变量从内存缓存到寄存器中,防止多个线程有可能使用内存中的变量,有可能使用寄存器中的变量,从而导致程序错误。
volatile关键字的使用场景? 能否和const一起使用?
volatile 关键字和 const 关键字可以同时使用,某种类型可以既是 volatile 又是 const ,同时具有二者的属性。
char arr[10] = "hello";
cout << strlen(arr) << endl; // 5
cout << sizeof(arr) << endl; // 10
void size_of(char arr[])
{
cout << sizeof(arr) << endl; // warning: 'sizeof' on array function parameter 'arr' will return size of 'char*' .
cout << strlen(arr) << endl;
}
int main()
{
char arr[20] = "hello";
size_of(arr);
return 0;
}
//输出结果:8 ;5
lambda 表达式的定义形式如下:
[capture list] (parameter list) -> reurn type
{
function body
}
其中:
lambda 表达式常搭配排序算法使用。
vector<int> arr = {3, 4, 76, 12, 54, 90, 34};
sort(arr.begin(), arr.end(), [](int a, int b) { return a > b; }); // 降序排序
for (auto a : arr)
{
cout << a << " ";
}
//运行结果:90 76 54 34 12 4 3
作用:用来声明类构造函数是显示调用的,而非隐式调用,可以阻止调用构造函数时进行隐式转换。只可用于修饰单参构造函数,因为无参构造函数和多参构造函数本身就是显示调用的,再加上 explicit 关键字也没有什么意义。
class A
{
public:
int var;
A(int tmp)
{
var = tmp;
}
};
int main()
{
A ex = 10; // 发生了隐式转换
return 0;
}
上述代码中,A ex = 10; 在编译时,进行了隐式转换,将 10 转换成 A 类型的对象,然后将该对象赋值给 ex。为了避免隐式转换,可用 explicit 关键字进行声明:
class A
{
public:
int var;
explicit A(int tmp)
{
var = tmp;
cout << var << endl;
}
};
int main()
{
A ex(100);
A ex1 = 10; // error: conversion from 'int' to non-scalar type 'A' requested
return 0;
}
const 的优点:
#define INTPTR1 int *
typedef int * INTPTR2;
INTPTR1 p1, p2; // p1: int *; p2: int
INTPTR2 p3, p4; // p3: int *; p4: int *
int var = 1;
const INTPTR1 p5 = &var; // 相当于 const int * p5; 常量指针,即不可以通过 p5 去修改 p5 指向的内容,但是 p5 可以指向其他内容。
const INTPTR2 p6 = &var; // 相当于 int * const p6; 指针常量,不可使 p6 再指向其他内容。
#define MAX(X, Y) ((X)>(Y)?(X):(Y))
#define MIN(X, Y) ((X)<(Y)?(X):(Y))
int var1 = 10, var2 = 100;
cout << MAX(var1, var2) << endl;
cout << MIN(var1, var2) << endl;
//程序运行结果:100 ;10
inline 是一个关键字,可以用于定义内联函数。内联函数,像普通函数一样被调用,但是在调用时并不通过函数调用的机制而是直接在调用点处展开,这样可以大大减少由函数调用带来的开销,从而提高程序的运行效率。
定义直接告诉你了所有的东西,这个变量是什么,这个函数是什么功能,这个类里面包含了什么东西。
声明就是指给除了当前变量或者函数,或者类什么的名字,不给其中的内容,就是先告诉你有这样一个什么类型的变量或者函数,但是这个变量或者函数的具体信息却是不知道的。
对于变量来说
定义:可以为变量分配存储空间,并且可以给变量一个初始值。
声明:告诉编译器这个变量的名字和类型(extern int a;(在没有赋值的情况下,变量前加上关键字extern一定为声明))。
对于函数来说
定义:就是这个函数具体的实现
声明:告诉编译器在这个程序中会有这么一个函数
简单来说,如果函数带有{},则其为定义;否则,就为声明。