C++98关键字:63个
一、命名空间规则(namespace)
1.命名空间可以解决函数、全局变量名重复的问题,包在不同的命名空间里的重复函数,实际就是两个完全无关的函数。
2.命名空间允许续嵌套:嵌套内部和外部没有任何关系。
3.命名空间允许重名:重名的命名空间就会被合并。
注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。它解决了C语言中只能用文件划分作用域的缺点。
命名空间使用有3种方式。
第1种:加命名空间名称及作用于限定符
int main() { printf("%d\n", N::a); return 0; }
第2种:(常用)
使用using关键字可以引用一个命名空间,使得这个命名空间中的所有内容对自己可见。
using N::b;
第3种:使用using namespace命名空间名称引入
using namespace N;
如果两个命名空间中分别有一模一样的两个函数(参数列表,函数名一样),那么即使同时用using引用了两个命名空间也不会报错。除非使用了两个函数,因为使用函数会产生歧义,也不知道该调哪个。
二、C++输入/输出(cout,cin)必须包含<iostream>,using namespace std;
c语言和C++关于函数方面的区别:
函数返回值类型,函数参数类型,C+函数参数可以带默认值,函数命名规则不同。
C++语言编译器比C对函数参数、返回值类型更加严格。
C语言没有显式写返回值类型时,C语言默认返回值类型为int。
三、 缺省参数:缺省参数是声明或定义函数时为函数的参数指定一个默认值。
在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
缺省参数不能在函数声明和定义中同时出现。
缺省值必须是常量或全局变量。
1.如果传入了,那么会取传入的值,如果没有传入,会取函数定义时被赋予的值(默认值)
2.这些参数必须位于参数列表的尾部,不能穿插
3.被赋值的参数列表不能在声明和定义中同时出现,只能出现一次。
4.函数的参数列表中,允许存在被赋值的参数,
分类:1全缺省参数、2半缺省参数(从右往左依次给出,不能间隔给)。
四、函数重载(在相同作用域中,函数名字必须相同,参数列表不同。)
C++允许两个重名函数同时存在,但他们的参数列表不能完全相同,(参数个数/类型/顺序)。
运行程序:预处理、编译、汇编、链接。
函数重载调用原理:
编译器在编译阶段,会对函数实参类型进行推演,根据结果找类型匹配的函数进行调用。如果有类型完全匹配的函数,则直接进行调用。如果没有类型完全匹配的函数,则会进行隐式类型转换,若隐式类型转换后,有对应的函数则调用,若没有就报错。
C语言不支持函数重载,C++到底是怎么支持函数重载?
1. C++和C语言编译器对函数名字修改规则不一样。
2.VS是一个集成开发环境,当点击生成或重新生成时:预处理-编译.obj-汇编-链接-生成可执行代码。
3.C/C++编译器对源文件是单独进行编译的,函数只有声明,没有定义可以编译通过,但不能链接。C语言只是加下划线_Add
4.C++编译器在编译函数时,会对函数名字进行修改,将参数类型添加到最终名字中,在代码层面,看起来函数名字相同,但编译器编译完成之后,底层使用的名字不一样。int Add(int left,int right)->?Add@@HHH@Z,double Add(double left,double right)->?Add@@YANNN@Z
1.名字修饰:
在C语言中,名字修饰只是在函数名前加下划线,所以只要函数名相同,就会导致冲突(C语言不支持函数重载的原因)。
在C++中,名字修饰是“?函数名@域名1@域名2@......@@参数列表@Z”的格式构成的,包含:
a、函数名
b、所在域
c、参数列表
所以C++中,以上三个必须完全相同,才会出现冲突,这就是函数重载的原理(底层处理)
2.extern“C”:将一段代码包起来,那么这段代码将会以C语言的风格进行编译。
五、引用(一般用于作参数或者返回值)
引用是给一个(变量)起别名,两个名字都是一个变量,所以操作谁从结果上看都一样。共用一块内存空间。注意const是常量。
引用是代替指针完成跨栈操作的,所以它具备指针跨栈的一切特点。
引用的底层实现实际是指针。
特点:引用在定义时必须初始化。
一个变量可以有多个引用。
一旦引用了一个变量,就不能再引用其他变量。
规避掉了野指针,空指针的过程。
void TestRef()
{ int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :"<< ret <<endl;
return 0;
}
输出结果是7。函数返回时,离开函数作用域后,栈上空间已经还给系统了。不能用栈上空间作为引用类型返回。
C语言中,传值:不能通过形参改变外部的实参,因为形参是实参的一份拷贝。
如果想要通过形参改变外部的实参,可以使用指针解引用。传地址。问题:指针可能不安全,--每次使用必须要指针判空,代码可读性较差。
2.引用和指针的不同点:
(1). 引用在定义时必须初始化,指针没有要求
(2). 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
(3). 没有NULL引用,但有NULL指针
(4). 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4 个字节)
(5). 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
(6).没有多级引用,但有多级指针
(7). 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
(8). 引用比指针使用起来相对更安全(指针可以改变指向,指针可以为空,野)
六、内联函数
调用时不创建新栈而直接在调用处展开的函数叫内联函数,关键字为inline(以空间换时间)。
内联函数一个对编译器的建议,如果函数过于复杂,编译器是会不接受你的建议,而处理成普通的函数。
inline可以代替带参宏。
宏是不安全的,能用inline就用inline。inline安全。
在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
编译器对 inline 函数的处理步骤
- 将 inline 函数体复制到 inline 函数调用点处;
- 为所用 inline 函数中的局部变量分配内存空间;
- 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
- 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO)
虚函数(virtual)可以是内联函数(inline)吗?
- 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
- 内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
inline virtual
唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如Base::who()
),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
宏的优缺点? 优点: 1.增强代码的复用性。 2.提高性能。
缺点: 1.不方便调试宏。(因为预编译阶段进行了替换) 2.导致代码可读性差,可维护性差,容易误用。 3.没有类型安全的检查 。
C++有哪些技术替代宏? 1. 常量定义 换用const 2. 函数定义 换用内联函数
七、Auto关键字(c++11):
1.修饰具有自动存储器的局部变量,
2.使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。
3.主要用于范围for:(用于遍历容器vector,数组)迭代的范围必须确定。
第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
4.使用auto声明指针类型时,auto与auto*无区别,声明引用时必须加&。
5.auto不能作为函数参数,不能声明数组。
八、范围for
for循环后的括号由冒号“ :”分为两部分:
第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
可用continue结束本次循环,可用break跳出整个循环。
迭代范围必须确定,数组第一个元素和最后一个元素的范围。
迭代对象要实现++,--操作。
九、指针空值nullptr
1.null被定义为字面常量0或者无类型指针的常量。nullptr代表指针空值常量。
2. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
3. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
4. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
十、const作用
- 修饰变量,说明该变量不可以被改变;
- 修饰指针,分为指向常量的指针(pointer to const)和自身是常量的指针(常量指针,const pointer);
- 修饰引用,指向常量的引用(reference to const),用于形参类型,即避免了拷贝,又避免了函数对值的修改;
- 修饰成员函数,说明该成员函数内不能修改成员变量。
十一、volatile
volatile int i = 10;
- volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改。所以使用 volatile 告诉编译器不应对这样的对象进行优化。
- volatile 关键字声明的变量,每次访问时都必须从内存中取出值(没有被 volatile 修饰的变量,可能由于编译器的优化,从 CPU 寄存器中取值)
- const 可以是 volatile (如只读的状态寄存器)
- 指针可以是 volatile
十二、类:类和对象
类是概念,对象是实体。
1、结构体在C和C++中的不同
(1).在C++中,用结构体定义变量 不需要加Struct,而C中不行。
(2).在C++中,可以定义空结构体,大小为1,而C中不行。
(3).在C++中,可以在结构体中声明甚至实现函数,在C中只能放函数指针。
(4).在C++中,成员函数直接可以访问本结构的成员变量而无需传入,在C中,函数和结构并无直接关联。
*成员函数不影响结构体的大小
*因为成员函数是放在公共区域的,只是在这个结构体域中而已。
2.类的定义:
12.类的定义:
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
访问限定符:public,protected,private
C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。
struct与class的区别:struct的成员默认访问方式是public,class是struct的成员默认访问方式是private。
类是一类特殊的结构体,在上述结构体中,加入public:,把struct改成class就成了一个简单的类。
面向对象的三大特性:封装、继承、多态。
访问限定符:
public:正常访问
private:能在类内访问
protected:目前跟private没有区别
tip1:访问限定符旨在编译阶段生效,编译好后,在运行阶段并没有限定。
tip2:结构体其实也能使用访问限定符,只是一般不去使用,因为它是用来兼容C结构体的。
tip3:结构体中默认是public,而类中默认是private
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。本质上是一种管理,使用protected/private把成员封装起来,开放公有成员函数访问。
十三、类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
如何计算类的大小:结构体的内存对齐原则
对齐规则是按照成员的声明顺序,依次安排内存,其偏移量为成员大小的整数倍,0看做任何成员的整数倍。
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8,gcc中的对齐数为4
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
最后结构体的大小为最大成员的整数倍 (所以这里的B中,最后结构体大小为double的倍数是24,而不是18)。
另:C语言中,空的结构体在内存中所占大小为0。(仅在gcc中测试为0)。
例如:
typedef struct
{
int a;
short b;
double c;
}A;
typedef struct
{
int a;
double b;
short c;
}B;
结果是sizeof(A)=16;sizeof(B)=24
一般不说明,都说得是32位编译器
为什么要内存对齐?
1.平台原因(移植原因):并不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
大端和小端是什么?
大端存储模式:就是内存的低地址上存着数据的高位,高地址上存着数据的低位。
小端存储模式:就是内存的低地址上存数据的低位,而高地址上存数据的高位。
为什么要有大小端?
在计算机系统中,规定:每个地址单元都会对应一个字节(8个bit),但是,在c语言中,除了有一个字节(8个bit)的char,也有两个字节(16个bit)的short,也有四个字节(32个bit)的long(在不同的编译器下可能不同)。对于16位或者32位的处理器,即就是大于8位的处理器,由于寄存器的宽度大于一个字节,那么就存在如何将一个多字节的变量的数据如何存放的问题——所以,就有了大小端之分。
十四、this指针(本质是一个成员函数的形参)
this指针是成员函数第一个隐含的指针形参。
一般成员函数中都含有一个this指针,这个指针指向调用这个成员函数的对象。
this指针的特性:
1. this指针的类型:类类型* const
2. 只能在“成员函数”的内部使用
3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
十五、类的6个默认成员函数
十六、构造函数(初始化)
它是一个特殊的成员函数,在对象的生命周期内只调用一次。并不是开空间创造对象,而是初始化对象。
特性:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
系统会提供一个默认的构造函数(缺省),如果自己实现了构造函数,则系统不再提供默认的构造函数。
构造函数可以存在参数,它与其他的构造函数是以 函数重载的方式共同存在的。
无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
再谈构造函数:初始化列表:引用成员变量、const成员变量
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
类类型成员(无默认构造函数)
拷贝构造函数指的是参数为本类其他对象的引用的构造函数,它在给对象初始化成本类其他对象时调用。系统会自动提供一个拷贝构造函数。
十七、析构函数(释放/置0)
析构函数是当一个栈被销毁前调用的,在C++中,当一个函数被销毁前会调用栈中每一个对象的析构函数。类似于C语言中Destory函数
其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(缺省)。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
十八、拷贝构造函数
只有单个形参,该形参是类类型对象的引用(常用const修饰),在用已存在的类类型对象
创建新对象时由编译器自动调用。
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
3.默认的拷贝构造函数对象按内存存储按字节序完成拷贝,叫做浅拷贝。
十九、运算符重载(增强代码可读性)
operator加上要重载的符号+、=等等。不改变运算符的优先级、操作数个数等等。
.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
1.运算符重载:将运算符看成函数,把他的几目当成参数,通过参数的类型识别出对应的操作方法。也是多肽的一种体现。相当于函数重载。
运算符重载有指定的规则,规则根据运算符来制定。
2.赋值运算符重载
(1). 参数类型
(2). 返回值
(3). 检测是否自己给自己赋值
(4). 返回*this
(5). 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。
二十、const成员函数
1.const加在成员函数的末尾,代表这个函数中的this是const修饰的
2.const对象或者 (成员函数)可以调用其他const函数(const成员函数),
非const对象或者(成员函数)可以调用非const成员函数和const成员函数(const成员函数)。
3.将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,不能对类的任何成员进行修改。
二十一、静态成员:
静态成员跟类走不跟对象走,类在它在。而一般成员是对象在他才在。所以静态成员可以通过类名直接调用,而普通成员必须通过对象调用。
静态成员也有private,public,protected之分
1、静态成员变量
(1)所有对象共享,无论谁改了,所有的一起改。
(2)存储在全局区,不占用类的空间,所以取sizeof的时候不算在内。
赋初值只能在类外,赋值时不加static,用“类名::变量名=n 直接赋值”
(3)不需要对象,可以直接用类名::调用
2、静态成员函数(在类外进行初始化)
1只能直接访问静态成员变量,无法访问其他的普通成员,因为他没有this指针
二十二、友元函数
友元就是让一个外部函数或者外部类能访问我的私有成员 。
友元打破了原有的权限制度,所以十分危险,不建议使用。
需要在类的内部声明,声明时需要加friend关键字。
说明:
(1)友元函数可访问类的私有成员,但不是类的成员函数
(2)友元函数不能用const修饰
(3)友元函数可以在类定义的任何地方声明,不受类访问限定符限制
(4)一个函数可以是多个类的友元函数
(5)友元函数的调用与普通函数的调用和原理相同
二十三、内部类
在另一个类内声明的类,它不属于外部类,调用需要使用域操作运算(::)符,同时它有权限的限制。(private的类声明不能直接在外面声明对象)内部类是外部类的友元,外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系
二十四、static
静态的成员变量一定要在类外进行初始化
特性:
1. 静态成员为所有类对象所共享,不属于某个具体的实例
2. 静态成员变量必须在类外定义,定义时不添加static关键字
3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值,const修饰符等参数
- 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。
- 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命名空间里的函数重名,可以将函数定位为 static。
- 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
- 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。