const
- 修饰变量
- 则变量不可修改 ,定义时必须初始化,修饰函数参数可以构成重载。
- 修饰(成员)函数
- 表示该函数不会修改类对象的数据成员,相当于const this,修饰this,修饰函数本身也可以构成重载。
- 修饰指针
- 按const与指针的位置关系,分常量指针和指针常量,常量指针表示指针是一个常量,不可修改,指针常量表示指针指向一个常量。
class A
{
private:
const int a; // const修饰成员变量,常对象成员,只能在初始化列表赋值
public:
A() { }; // 构造函数
A(int x) : a(x) { }; // 初始化列表
// 这里的const可用于对重载函数的区分
int getValue(); // 普通成员函数
int getValue() const; // const修饰成员函数,常成员函数,不得修改当前对象中的任何数据成员的值
// 本质就是修饰了this指针,使它指向一个常量。
};
void function()
{
A b; // 普通对象,可以调用全部成员函数
const A a; // const修饰变量,常对象,只能调用常成员函数、更新常成员变量
const A *p = &a; // 常指针,指针所指对象是常量
const A &q = a; // 常引用,引用对象是常量
char greeting[] = "Hello";
char* p1 = greeting; // 指针变量,指向字符数组变量
const char* p2 = greeting; // 指针变量,指向字符数组常量
char* const p3 = greeting; // 常指针,指向字符数组变量,指针本身是个常量,不可修改,也就是说一直指向greeting
const char* const p4 = greeting; // 常指针,指向字符数组常量,在上面的基础上,再加上所指向greeting也不可修改
}
void function1(const int Var); // 传递过来的参数在函数内不可变
void function2(const char* Var); // 参数指针所指内容为常量,保证Var所指对象在函数内部不会改变
void function3(char* const Var); // 参数指针为常指针,保证var值不会改变
void function4(const int& Var); // 引用参数在函数内为常量,保证var所引用对象在函数内部不会改变
const int function5(); // 返回一个常数
const int* function6(); // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const function7(); // 返回一个指向变量的常指针,使用:int* const p = function7();
const、非const函数/变量之间的互调,非const可以使用const,const不可以使用非const,很好理解,低安全性的变量可以使用高安全性的变量。
const和指针组合,从右往左解读。
static
- 控制存储
- 在程序内存的全局区
控制作用域
- 只在声明所在作用域内有效。
修饰变量(静态变量)
- 存储
- 存储在全局区,具备全局变量的特点,在main函数之前分配空间并初始化,没有显式初始化,则执行默认初始化。
- 当是成员变量,则所有对象只保存一个该变量。可通过类直接访问。
- 作用域
- 在文件内(函数外)声明,则作用域是当前文件。
- 如果是在函数内声明,则作用域是该函数内。
- 存储
- 修饰函数(静态函数)
- 作用域
- 只在声明的文件中可见。
- 当是成员函数,可通过类直接访问。
- 存储
- 能够访问的外部变量只能是全局变量。
- 作用域
// 以下代码在fuck.h中
static int a; // 静态变量,执行默认初始化,作用域fuck.h文件
static int a = 1; // 静态变量,初始化只会执行一次,作用域fuck.h文件
int shit()
{
static int count = 1; // 静态变量,函数调用多次,初始化只执行一次,作用域shit函数
}
static int fuck(); // 静态函数,只能访问的外部变量是静态变量。
class A
{
public:
static int a; // 静态成员变量,默认初始化
public:
static void fuck(); // 静态成员函数
}
inline
inline修饰的函数称为内联函数。
原理和宏一样,但是相比于宏,内联函数有函数特性,即有类型检查。多用于修饰一些函数体较为简单的函数。不能包含循环、递归、switch。
类声明中定义的函数都隐式转换成内联函数,虚函数除外。
inline是对编译器的请求,不一定会真正生效,比如对虚函数进行内联,则可能不会生效。
inline int fuck(...); // 声明1(加 inline,建议使用)
int fuck(...); // 声明2(不加 inline)
inline int fuck(...) {/****/};
class A {
int fuck() { return 0; } // 隐式内联
inline virtual fuck_virtual(); // 虚函数内联?内联可能不会生效
}
inline int A::fuck() { return 0; } // 需要显式内联
// 内联发生在编译阶段,而虚函数多态发生在运行时阶段。
// 如果fuck_virtual是以多态的形式调用,在编译阶段无法确定对象类型,也就无法确定调用哪块代码
// 也就不能内联。
inline int A::fuck_virtual() { return 0; }
A a;
a.fuck_virtual(); // 内联生效,因为并没有触发多态,可以确定对象类型
A* a;
a->fuck_virtual(); // 内联不生效,触发多态,不能确定对象类型,。
强制类型转换符
类型转换是有风险的,以前C的转换非常简单,前面加个括号,这种做法不利于分辨类型转换的风险,也不利于问题查找,C决定提供几个不同等级的类型转换,每种转换用于不同场景,如果用错就会编译报错,提醒程序员这个类型转换的风险。
4个类型转换运算符:
- static_cast
- 用于风险低的类型转换,这种转换不会出现什么严重问题,一般是一些隐式类型转换,如数值类型间的转换,子类转换成父类(向上转换是隐式转换)
- dynamic_cast
- 用于多态类型转换,只适用指针或引用。转换失败就返回nullptr,安全性高。
- 多态类型转换:子类和父类之间互相转换。
- 可能会抛出bad_cast异常。
- const_cast
- const与非const、volatile与非volatile的类型转换。
- reinterpret_cast
- sizeof
- C++运算符。用于获得变量/对象/类型的字节数。
- 在编译阶段计算长度。
- 易搞错情况,sizeoof(数组)获得数组大小,注意是数组,不是数组指针,后者返回的是指针大小。
- strlen
- c库函数,在
中声明。返回字符串的长度(以\0结尾,不包含\0字符)。 - 在运行时计算长度。
- c库函数,在
A a; // 类型A可以是任意类型
A& b = a; // 引用
A* c = &a; // 指针
A array[10]; // 数组
int size = sizeof(A) // A类型的字节数
int size = sizeof(a); // A类型的字节数
int size = sizeof(b); // A类型的字节数
int size = sizeof(c); // 一个指针的字节数,4/8个字节,平台各异。
int size = sizeof(array); // 数组的大小,10 * sizeof(A)
void fuck(A a[], int length)
{
int size = sizeof(a); // 这是指针大小,数组是转成了指针进行参数传递。
}
// strlen源代码
size_t strlen(const char* str){
size_t size_ret = 0;
while(*str++) ++size_ret;
return size_ret;
}
sizeof(类)是计算类对象的大小,考虑因子如下:
- 考虑内存对齐填充的字节数
- 考虑非静态成员变量
- 静态成员变量在全局区,并不在对象内存中。
- 考虑是否为有虚函数
- 是否有虚函数,决定了类对象还不会有虚函数表指针。
- 考虑是否是虚继承
- 空类大小为1个字节
// 假设在64位编译器中编译,对齐基数为8字节
// A的成员变量首地址必须被这个数整除,min(对齐基数,A中最宽基本类型大小) = 4字节。
class A {
private:
static int s_var; // 不影响类对象大小
const int c_var; // 4字节
int var; // 4字节(上) + 4字节(var) = 8字节
char var1; // 8字节(上)+ 1字节(var1)+ 3字节(填充)= 12字节。
public:
A(int temp) : c_var(temp){} // 成员函数,不影响
~A(){} // 成员函数,不影响
virtual void f() { cout << "A::f" << endl; } // 有虚函数,则每个类对象都会有一个虚函数表指针
virtual void g() { cout << "A::g" << endl; } // 多个虚函数,也都是只添加一个虚函数表指针
virtual void h() { cout << "A::h" << endl; }
}
int main(){
A a(4);
A *p;
cout << sizeof(p) << endl; // 8字节,指针大小。
cout << sizeof(ex1) << endl; // 24字节
return 0;
}
#pragma pack(n)
设定结构体、联合以及类成员变量以 n 字节方式对齐。
#pragma pack(push) // 保存对齐状态
#pragma pack(4) // 设定为 4 字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop) // 恢复对齐状态
explicit
只用于修饰转换构造函数(单参数),声明该构造函数必须显示调用而不能隐式调用,即不能进行隐式转换。
无参和多参构造函数肯定都是显式调用,无所谓显式隐式,修饰是可以修饰,但是没有任何意义。
class A {
public:
explicit A(int a);
}
class B {
public:
explicit B(int a);
}
int main(){
A a(10); // 显式调用
B b(10); // 显式调用
A a = 10; // 错误,已声明为显式调用。
B b = 10; // 隐式调用
}
volatile
告诉编译器,变量的值会被编译器未知的因素(操作系统、硬件、其他线程)修改。防止因编译器的优化导致的错误。比如优化之后变量的值可能缓存到寄存器,而变量内存的值可能被修改,这时候值就是错误的了。因此volatile会请求编译器每次都是从内存中取值。
位域(bitfield)
类的成员是bit为单位(可以1字节以下),可以节省空间。不过它的内存布局是机器相关的。
类可以将其(非静态)数据成员定义为位域(bit-field),在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。
C++_固有的不可移植特性
assert
断言,是宏。
// 在C中,定义在<assert.h>中
// 在C++中,定义在<assert>中
#define NDEBUG // 在头文件之前定义,则assert将不生效。
#include <assert>
assert(p != nullptr); // 为false,则立即终止程序
extern “C”
告诉编译器,这块代码用C的方式进行编译链接,就是这段是C代码。
- 被
extern "C"
修饰的变量和函数是按照 C 语言方式编译和连接的
extern "C"
的作用是让 C++ 编译器将 extern "C"
声明的代码当作 C 语言代码处理,可以避免 C++ 因符号修饰导致代码不能和C语言库中的符号进行链接的问题。
#ifdef __cplusplus
extern "C" {
#endif
void *memset(void *, int, size_t);
#ifdef __cplusplus
}
#endif
using
using std::cin; // using声明,引用命名空间的指定成员
using namespace std; // using指示,引用命名空间的所有成员,尽量少用。
// 尽量少使用using,因为是全部导入,会有多余不用的,另外覆盖了也不会有警告,也难以统计成员使用情况。
class Derived : Base {
public:
using Base::Base; // 在Derived中生成所有和Base中一一对应的构造函数(参数列表相同)
};
#inlcude””与 #inlcude<>
搜索路径的策略不同。
#include<>是在系统配置的库环境中查找
#include””则是先从用户目录开始查找,如果没找到再从系统目录查找。
因此C库文件一般用#include<>,用户自定义文件一般用#include””
#pragma once
// 下面这称为include防范,宏防范,避免重复引入。
#ifndef _FUCK_H_
#define _FUCK_H_
class Fuck {}
#endif
// ****************************************
// 上下的作用相同,显然下面的要更简单。
// ****************************************
// 注意,#pragma once并不是C/C++标准,但是一个广泛被支持的前置处理符号
#pragma once
class Fuck{}
lambda
匿名函数,本质就是一个重载了调用运算法的匿名类。如果是值传递,则类内部有自己的数据成员。
C++_lambda表达式
final
class Base final { // final用途一:阻止继承
public:
virtual void func1() final; // final用途二:阻止虚函数重载
}
class Derived : public Base { // 错误:Base is final class
public:
virtual void func1() { // 错误:func1 is final class
......
}
}