(1)bool类型也叫逻辑类型,是个2值enum,值为true或false(这2个也是C++关键字)
(2)C语言没有bool关键字,不源生支持bool类型,一般用typedef int bool;这样来自定义
(3)C++语言源生支持bool类型,一般占1字节(平台相关),用法没什么差异
(4)bool内建和自定义至少有一个差别:函数重载机制认为bool是不同类型
#include
using namespace std;
int main(void)
{
int a = 4;
bool b1 = !a;
cont << b1 <<endl; //输出0
cout << boolalpha << b1 << endl; //输出false
return 0;
}
1、char
(1)字符类型,一般占1字节,表示字符(ASCI或unicode字符)
(2)从C++14开始char默认是unsigned还是signed取决于目标平台,如arm默认unsigned,而X64默认是signed,建议如果在意符号最好显式使用unsigned char或signed char
(3)char类型cout输出默认为字符,而int类型cout输出默认为数字
(4)1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
2、wchar_t
(1)宽字符,用于应对一个字符编码超过1字节的Unicode编码
(2)wchar_t和char的数组都能存下unicode码,区别是需要几个单元才能存一个字
(3)wchar_t占几个字节取决于具体实现,可能是unsigned short也可能是int
(4)wchar_t要用wcin和wcout来输入输出,对应字符串为wstring
3、指定具体字节数的字符类型
(1)char8_t (C++20 起) , char16_t (C++11 起) , char32_t (C++11 起)
(2)这三个类型一个套路,最大特征就是明确指定占用字节数,且都是无符号的
(3)char8_t很大程度上等同于unsigned char
(4)C++20起,新增字符串类u8string, u16string, u32string
1、C++中无明显变化的关键字
if
else
for
do
while
break
continue
switch
case
default
goto
return
unsigned
signed
float
double
short
int
long
void
sizeof
register
volatile
extern
typedef
asm
2、C++中新增的运算符代用关键字
(1)逻辑运算代用关键字
and -------> &&
or -------> ||
not -------> !
(2)位运算代用关键字
bitand-------> &
bitor-------> |
xor -------> ^
and_eq -------> &=
or_eq -------> |=
xor_eq -------> ^=
compl -------> ~
(3)不等判断运算符代用关键字
not_eq -------> !=
(4)运算符代用关键字的优势:有些人认为这样便于理解和阅读
1、C++继承C的枚举用法
(1)典型枚举类型定义,枚举变量定义和使用
(2)枚举类型中的枚举值常量不能和其他外部常量名称冲突:举例1宏定义,举例2另一个枚举
// C和C++98等老版本里的定义和使用
enum day {MON, THU, WEN};
enum day d1; // 定义了一个day类型的变量,变量名是d1
/**********************************/
//C中习惯用typedef来重命名类型以避免每次类型使用都加enum
typedef enum {MON, xxx, xx} day;
day d1;
/*********************************/
//枚举类型中的枚举值常量不能和其他外部常量名称冲突
enum day {MON, THU, WEN};
enum day2 {MON, xxxx, yyy};
#define MON "55" //会报错
2、C++11中扩展的枚举
(1)enum class enumType:valueType{one=xx, two, three};
(2)两种简写
(3)解决2个枚举中的重名问题,但是宏定义仍然不能重名
/*********************************/
// C++11开始enum支持新的写法
// 完全写法
enum class day:unsigned int{MON = 44, THU, WEN};
// 简化写法1
enum class day{MON, THU, WEN};
// 简化写法2
enum day{MON, THU, WEN};
//使用方法
day d1; // C++中定义时可以省掉前面的enum
d1 = day::MON;
/********************************/
参考博客:C++新增的引用
1、C语言中union回顾
(1)union翻译成共用体更合适,而不是联合、联合体
(2)union中所有成员是多选一的关系,这是union和struct的最大差别
(3)union的典型用法是测试大小端,面试笔试常考,必须掌握(具体可参考我的博客C语言高级专题)
2、C++中union和C中不同
(1)C++中union类型定义后使用时可以省去union(和上节enum时一样)
(2)C++中union里成员除了普通的,还可以是对象,但是对象不能包含自定义构造函数、析构函数,简单说就是不能太复杂
(3)C++中经常用到匿名union,一般是内置在class内部做成员变量
(1)union在C++中没有突出变化,主要还是沿用C中使用
union myu // union类型的定义
{
char *p;
函数指针p1;
};
union myu m1; // C中定义了一个myu类型的变量
myu m1; // C++中定义了一个myu类型的变量
(2)匿名union
union
{
char *p1;
int *p2;
}m1; // 直接定义了union变量m1
1、C中inline使用关键点强调
(1)inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”,所以关键字 inline 必须与函数定义体放在一起,而不是和声明放在一起
(2)如果希望在多个c文件中使用,则inline函数应该定义在h文件中(不需要额外声明);如果只在一个c文件中使用,则inline函数可以定义在c文件或h文件中(若定义在c文件时可以声明到h文件中去,声明时可以不加inline)
(3)inline函数在项目中可以多次定义,只要函数体完全相同且在一个c文件范围只定义一次
(4)inline只是一种对编译器的建议而不是强制,所以inline函数不一定真被inline
(5)递归函数不应该被声明为inline,超过一定长度(通常是10行)的函数不应该被inline,内含循环的函数不建议被inline
2、C++中inline新增的特性
(1)定义在类声明之中的成员函数将自动地成为内联函数,例如如下代码:
class A
{
public:
void Foo(int x, int y) // 自动地成为内联函数,即使没有inline关键字
{
}
}
(2)如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上 inline,否则就认为不是内联的。值得注意的是:如果在类体外定义inline函数,则心须将类定义和成员函数的定义都放在同一个头文件中,否则编译时无法进行置换。
class B
{
int add(int a ,int b);
}
inline int add (int a,int b)
{
return a + b ;
}
1、C语言中的NULL
(1)NULL用来标记野指针
int *p = NULL;
if (NULL != p)
{
// 解引用指针p
}
(2)NULL在C和C++中的定义为什么不同?因为C++不允许void *隐式转为int *等类型
(3)C++中也可以继续用NULL,但是因为函数重载的引入,NULL传参会带来歧义
#include
using namespace std;
// C语言中NULL就是(void *)0
// C++语言中NULL就是0
void func(int a)
{
cout << "func int a" << endl;
}
void func(int *p)
{
cout << "func int *p" << endl;
}
int main(void)
{
int a = 1;
int *p = &a ;
func(a);
func(p);
func(NULL); //编译发生错误,有歧义
return 0;
}
2、nullptr如何工作
(1)nullptr传参,表示真正的空指针
(2)nullptr的本质:
const class nullptr_t{
public:
template<class T> inline operator T*()const {return 0;}
template<class C, class T> inline operator T C::*() const {return 0;}
private:
void operator&() const;
} nullptr={};
思考:NULL和nullptr到底有什么区别
3、nullptr的评价
(1)C++11开始可用,注意版本要求,编译时加编译选项 -std = c++11
(2)实践中在判断野指针时很多人还是喜欢if (!p)这样···
(3)nullptr无法解决char p和int p这样的传参重载问题,所以还是有点不完美
(4)nullptr不属于任何一种对象指针,但是却可以表示任何类型的空指针**
1、C中的断言assert
(1)直接参考:assert参考博客
(2)C的assert是运行时检测发现错误,而不是编译时
(3)C在编译时错误用#error来输出
2、C++静态断言
(1)C++引入static_assert(表达式, “提示字符串”)来实现编译时的静态断言
(2)实例演示
#include
using namespace std;
// 假设:我写了一个项目只能运行在32位系统上,我要防止用户在64位系统上运行该程序
int main(void)
{
static_assert((sizeof(void *) == 4), "not support non 32bit system");
return 0;
}
3、静态断言主要用途
(1)static_assert主要用于检查模板参数是否符合期望
(2)C++20中引入了concept来进一步更好的实现模板参数的编译时类型匹配检查
1、C语言中内存对齐关键点
(1)#pragma pack(),和 __ attribute __((aligned(n)))
2、C++中内存对齐新增关键字
(1)alignof (C++11 起)
(2)alignas (C++11 起)
3、实战演示
#include
using namespace std;
#pragma pack(1)
struct s1
{
char a; // 1 +3 4
int b; // 4 +0 4
double c; // 8 +0 8
};
#pragma pack()
struct s2
{
char a;
short b;
short c;
}__attribute__((aligned(32)));
int main(void)
{
// alignof有点像sizeof,用来测一个类型或变量的对齐规则
cout << "alignof s1 = " << alignof(s1) << endl; //1
cout << "sizeof s1 = " << sizeof(s1) << endl; //13
cout << "alignof s2 = " << alignof(s2) << endl; //32
cout << "sizeof s2 = " << sizeof(s2) << endl; //32
return 0;
}
4、什么情况下需要人为改变/指定对齐方式
(1)往大去对齐。有时候会有一些硬件特殊要求,譬如MMU,cache等。用__ attribute __((aligned(n)))实测ok,用#pragma实测不ok
(2)往小去对齐。有时候需要节省内存而浪费效率,所以希望忽略内存对齐,紧密排放。用#pramgma实测ok,用
__ attribute __((aligned(n)))实测不ok
5、alignas的用法
(1)使用方法:一般在类型定义时,放在名称前
#include
using namespace std;
struct alignas(32) s3
{
char a; // 1 +3 4
int b; // 4 +0 4
double c; // 8 +0 8
};
int main(void)
{
cout << "alignof s3 = " << alignof(s3) << endl; //32
cout << "sizeof s3 = " << sizeof(s3) << endl; //32
return 0;
}
(2)效果:和__attribute__((aligned(n)))效果一样
2、typeid
(1)typeid是一个运算符,类似于sizeof
(2)typeid定义在头文件typeinfo中,必须包含该头文件
(3)typeid用来返回一个变量(表达式)(对象)的类型
(4)typeid使用实战
#include
#include
using namespace std;
int main(void)
{
// int a; // int类型的type.name是 "i"
// char a; // char类型的type.name是 "c"
// unsigned char a; // unsigned char类型的type.name是 "h"
signed char a; // signed char类型的type.name是 "a"
unsigned char b;
if (typeid(a) == typeid(b))
cout << "==" << endl;
else
cout << "!=" << endl;
cout << "a type = " << typeid(a).name() << endl;
return 0;
}
3、typeid的深层次说明
(1)一个表达式的类型分静态类型和动态类型,分别对应编译时和运行时类型决策系统
(2)typeid可用来返回静态类型,也可用来返回动态类型
(3)typeid是C++语言本身的特性,由编译器和库函数共同支撑
(4)typeid真正大用在引入class和继承后,并结合指针和引用后才能显现出来
参考博客:C++的4种cast转换
1、auto关键字
(1)auto在C中修饰局部变量,可以省略,完全无用。C++中的auto完全是一个新关键字
(2)auto要求至少不低于C++11标准来支撑
(3)auto在编译器由编译器帮我们自动推导出变量(对象)类型,所以定义时必须初始化
(4)auto可以一次定义多个同类型的变量,但是不能一次定义多个类型不同的变量,这是auto的类型推导机制决定的。
int i = 5;
auto i = 5; // 编译器在编译时自动帮我们推导出i的类型是int
int a, b;
auto a = 4, b = 5; // 正确
auto a = 4, b = 5.5; // 错误
2、decltype关键字
(1)C++11新增关键字
(2)decltype可以让编译器推导目标表达式的类型作为一种类型符使用
(3)decltype(表达式)作为类型定义变量不要求初始化
double i = 5;
decltype(i) j; // 定义了变量j,类型是和i相同
3、auto和decltype的对比
(1)auto忽略顶层const,而decltype则保留const
(2)auto作为类型占用符,而decltype用法类似于sizeof运算符
(3)对引用操作,auto推断出原有类型,decltype推断出引用
(4)对解引用操作,auto推断出原有类型,decltype推断出引用
(5)auto推断时会实际执行,decltype不会执行,只做分析。
const int i = 5;
decltype(i) j = 8;
j = 9; // 编译器报错,因为j是const的
const int i = 5;
auto j = i;
j = 4; // 编译器不报错,auto忽略顶层const
1、struct和class
(1)struct是C中用户自定义类型,主要功能是对功能相关数据的封装
(2)struct不能直接封装函数,但可以通过封装函数指针来间接封装函数
(3)struct就是class的初级阶段,class在struct基础上做了很多扩展,便有了面向对象
2、访问权限
(1)类是对数据(成员变量)和方法(成员函数)的封装
(2)封装的一个重要特征就是访问权限管控,本质是为了隐藏实现细节,避免意外篡改
(3)C++支持三个访问管控级别:private、protected、public
3、C++的对象创建和销毁
(1)对象的本质等同于C中的变量,对象的创建和销毁也就是变量的产生和销毁,本质上是变量对应的内存地址的分配和释放归还
(2)C中全局变量和局部变量都是自动分配和回收内存的,堆内存需要用户手工申请和释放(malloc和free调用)
(3)C++中因为变量升级成了对象,涉及到构造函数和析构函数,因此malloc和free升级为了new和delete
(4)C++中仍然大量使用全局变量和局部变量,但是动态分配占比例越来越多。这是业务特点决定的,不是C++语言决定的。语言只是提供机制,业务才决定策略。
1、static在C中的用法
(1)静态全局变量和函数,限制链接属性。C++中建议优先使用命名空间机制替代
(2)静态局部变量,更改地址域和生命周期。C++中继续沿用。
2、static在C++中新增用法
(1)用在class中,有静态数据成员和静态成员函数
(2)简单理解:静态成员和方法是属于class的,而非静态是属于对象的
(3)静态类往往用在单例模式中,实际上和面向对象的思想有所差异
(4)要真正理解静态类,得先学习面向对象和普通非静态类后才可以
3、this关键字
(1)本质是个指针,指向当前对象
(2)this的主要作用是让我们在未定义对象前可以在方法中调用对象里的成员
(3)this的深度讲解在第2部分,包括具体使用和实现原理
#include
using namespace std;
class A
{
public:
// 成员变量
int i;
// 非静态成员函数 方法
void func1(void);
// 静态成员变量
static int j;
// 静态成员方法
static void func2(void); // static是一个声明性的
};
void A::func1(void)
{
// cout << "A::func1, i = " << i << endl; // 成员函数中访问成员变量
cout << "A::func1, i = " << this->i << endl; //实质是通过this指针找到的成员变量i
}
void A::func2(void)
{
cout << "A::func2" << endl;
}
int main(void)
{
A::func2(); //直接输出A::func2,不用定义对象,说明静态成员函数是属于class的
A a; // 首先要定义一个对象,没有对象就无法访问成员
a.i = 34; // 在外部访问class中普通成员变量的方法
a.func1(); // 在外部访问class中普通成员函数的方法
//输出 A::func1, i = 34 ,说明非静态成员函数是属于对象的
return 0;
}
1、面向对象允许类的继承机制
(1)C++中用:来表示继承关系,有些编程语言有extends关键字表示继承关系
(2)virtual修饰class的成员函数为虚函数,一般在基类中,只有接口声明没有实体定义
(3)基类的virtual成员可以在派生类中override重写,以实现面向对象的多态特性
(4)注意区分重写override与重载overload
(5)override关键字是C++11引入,用来在派生类中成员函数声明时明确表明需要派生类去重写的那些成员方法,这样如果程序员在成员方法实体定义中做的不对编译器可以报错提醒
2、继承的终止final
(1)一个class不希望被继承(不想做父类),可以定义时用final修饰
(2)一个成员方法不希望被子类override,可以声明时用final修饰
(3)final是C++11引入的
(4)很多其他面向对象语言如java中也有final关键字,也是这个作用
3、using关键字
(1)用法1就是using namespace std;这种
(2)用法2与class的继承和访问权限限制有关,属于一个声明,能够让private继承的子类去声明并访问父类中本来无权限访问的成员
4、operator
(1)用于运算符重载,也就是为一个class重定义某种运算符
5、friend
(1)让不属于一个class的外部函数也能访问class内受保护的成员变量
(2)实际上是对面向对象的一种扩展或者说破坏,在面向对象深入理解之后再来学习更好
6、explicit
(1)本意为显式的,对应implicit隐式的
(2)用来修饰只有一个参数的构造函数,以阻值构造函数不合时宜的类型转换
(3)很简单一个特性,第2部分再详解
1、C语言中const用法回顾
(1)const变量,比宏定义的优势是带数据类型,可以让编译器帮我们做类型检查
(2)const数组,和常量变量类似
(3)const指针,三种情况:const int *p, int * const p, const int const p;(看const后面是p代表指针指向的内容是const的,const后面的是p就代表p本身是const的)
2、C++中const新增用法
(1)const引用,主要用于函数传参,限制函数内部对实参进行修改
// C++中更倾向于使用引用而不是指针
// 调用时,int i; func2(i);
int func2(const int &a)
{
if (a > 100)
return 0;
else
return -1;
}
(2)const成员函数,限制函数内部对类的成员变量的修改
#include
using namespace std;
class A
{
public:
int i;
int func6(void) const ; // const成员,明确告知func6内部不会修改class A的成员变量的值
};
int A::func6(void) const
{
//this->i = 5; //编译会报错,const不允许修改成员变量的值
cout << "A::func6, i = " << this->i << endl;
}
int main(void)
{
A a;
a.func6();
return 0;
}
1、mutable
(1)mutable用来突破const成员函数的限制,让其可以修改特定的成员变量
(2)案例参考:mutable案例参考博客
2、constexpr
(1)用法如下:
constexpr int multiply (int x, int y)
{
return x * y;
}
const int val = multiply( 10, 10 ); // 将在编译时计算
const int val = 100; //编译器自动优化成
(2)本质上是让程序利用编译时的计算能力,增加运行时效率
(3)由C++11引入,但是实际有一些编译器并不支持,需实际测试
3、C++20新引入的2个
(1) constinit
(2) consteval
1、模(mu)板编程初体验
(1)template和typename
(2)模板实际上是一种抽象,C++的高级编程特性就是不断向抽象化发展
#include
using namespace std;
/*
// 写一个函数add,完成2个数字的加和
// 假如有10种数据类型要考虑,那就要写10个add的重载函数,非常低效
int add(int a, int b)
{
return (a + b);
}
double add(double a, double b)
{
return (a + b);
}
*/
// 自定义一个抽象类型,譬如命名为X,编程的时候用X编程,X的具体类型在调用函数时
// 由实参的类型来确定
template <typename T>
T add(T a, T b)
{
return (a + b);
}
int main(void)
{
char i = 45, j = 6;
cout << "a + b = " << add(i, j) << endl;
return 0;
}
2、export
(1)用来在cpp文件中定义一个模板类或模板函数,而它的声明在对应的h文件中
(2)export专用于模板,类似于extern之于简单类型
(3)实际很多环境不支持,暂不必细究,看到代码时能认出即可
3、requires
(1)C++20引入,用于表示模板的参数约束
(2)了解即可,暂时不用管
1、何为异常处理
(1)异常exception,即运行时错误
(2)C中没有异常机制,所以运行时遇到错误只能终止程序
(3)C++中新增了异常处理机制,允许程序在运行时拦截错误并处理,这样程序就不用终止
(4)异常机制的一个典型案例就是:由用户输入2个数字然后相除中的除0异常
2、异常处理编程实践
(1)try, catch, throw
#include
using namespace std;
int main(void)
{
// 让用户输入2个数,然后程序返回他的相除
cout << "please input 2 numbers" << endl;
int m, n;
cin >> m >> n;
// C++中用异常处理机制来处理
try
{
// try括号里的代码就是有可能触发异常的代码
if (n == 0)
throw ('A');
cout << "m / n = " << m/n << endl;
}
catch (int e) // catch的()里写上要抓取的异常类型
{
dosomething(); //处理异常
}
catch (double e) // catch的()里写上要抓取的异常类型
{
dosomething2(); //处理异常
}
/*
// C中我们这样处理
if (n == 0)
{
cout << "0 not good" << endl;
return -1;
}
else
{
cout << "m / n = " << m/n << endl;
}
*/
cout << "---other code---" << endl;
return 0;
}
(2)异常处理机制为什么优于出错条件判断参考博客
3、异常和函数
(2)throw一个异常后如果没有catch会层层向外传递直到被catch为止
(3)函数可以用throw列表来标识自己会抛出的异常
void func(void) throw(A, B, C); // 这种声明就是告诉调用者func有可能抛出3种异常
4、标准库中的exception类
(1)标准库中定义的异常类及其派生类,很多内置代码的错误会抛出这些异常
(2)譬如bad_typeid,使用 typeid 运算符时,如果其操作数是一个多态类的指针,而该指针的值为 NULL,则会拋出此异常
(3)譬如bad_cast,用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常
5、noexcept关键字
(1)throw(int, double, A, B, C)表示函数可能会抛出这5种类型的exception
(2)throw() 表示函数不会抛出任何异常
(3)C++11中引入noexcept关键字替代throw()表示函数不会抛出任何异常,也可以使用noexcept(bool)表示有无异常
(4)没有throw列表的函数,表示函数可能会抛出任意类型的异常
1、剩余一些关键字
(1)线程相关:thread_local (C++11 起)
(2)import和module (C++20)
(3)协程相关:
co_await (C++20 起)
co_return (C++20 起)
co_yield (C++20 起)
(4)并发相关:synchronized (TM TS)
(5)反射相关:reflexpr (反射 TS)
(6)其他:
transaction_safe (TM TS)
transaction_safe_dynamic (TM TS)
atomic_cancel (TM TS)
atomic_commit (TM TS)
atomic_noexcept (TM TS)
2、总结
(1)C++关键字和复杂度远超过C语言,语言特性较多
(2)面向对象编程相关特性是C++的基础核心,占比非常大
(3)模板泛型和抽象化编程是C++的重要特征,甚至可以说是精髓所在
(4)和java、python相比,C++的语法细节过多,这也是C++较难学习的重要原因
(5)不要试图去记,以理解为主,配合代码实验去消化吸收,形成自己对C++的认知
(6)经典C++与C++11、14、17、20的差异其实就是相应增加的关键字带来的新语言特性