名字空间
1.C++对程序中的标识符(类型、函数、变量)按照某种逻辑划分成若干个组
对标识符的逻辑划分
cin cout endl C++标准库的函数,对象,类型都位于std名字空间中
避免了名字冲突
2.定义名字空间的语法
namespace 名字空间的名字{
全局变量
类型定义
函数
}
3.使用名字空间
(1)作用域限定符 名字空间名::标识符
表示明确访问是特定名字空间中的特定标识符
最麻烦
(2)名字空间指令
using namespace 名字空间名;
该语句可以在任何地方
表示在该指令之后,对指令所指的名字空间中所有标识符对当前作用域都可见
接下来都可以直接访问名字空间中的标识符,不需要加"名字空间名::"
当然也可以加
*对当前作用域可见
(3)名字空间声明
using 名字空间::标识符
将指定名字空间中的某个标识符(成员)引入到当前作用域,可以直接访问
*导入到当前作用域
4.匿名空间
编译器为没有指明名字空间的全局区的标识符置于匿名名字空间中
如果要指明访问匿名名字空间中的标识符 直接用 ::标识符
:: 作用域限定符
名字空间名::标识符 访问指定名字空间中的标识符
::标识符 访问匿名名字空间中的标识符 可以区分同名的全局变量和局部变量
5.名字空间合并
一个程序中,如果名字空间名字相同,则认为是同一个名字空间
里面的标识符会进行合并
6.名字空间嵌套
一个名字空间包含另外一个名字空间
不能直接 using namespace 里层名字空间; 对于当前作用域而言是不可见
using namespace 外层名字空间;
using namespace 里层名字空间;
using namespace 外层名字空间::里层名字空间;
注意:
如果用using namespace 名字空间;进行名字空间声明当前作用域可见
如果不同的两个作用域中含有相同名字和标识符,不访问没问题
但是如果用直接访问,则会产生歧义
using namespace 名字空间; 进行声明不会有歧义
using 名字空间名::标识符 可能会有歧义
7.名字空间在多文件中进行编程
结构体、联合和枚举
1.结构体
(1)C++中在使用结构体时,直接可以省略struct关键字
(2)sizeof(空结构体) C++结果为1 C语言中0
(3)C++结构体中可以定义函数,结构体中的函数可以直接访问成员
C++结构体中的函数,是用C结构体中可以定义函数指针的方式实现的
2.联合
C++中支持匿名联合。
借用联合的语法形式,描述变量在内存中的布局方式
匿名联合的变量,可以直接访问,不需要定义联合变量
3.枚举
C++中的枚举是一种独立的类型 不能直接用整数值赋值给枚举变量
依然允许枚举变量/枚举值赋值给整数变量
函数
1.支持重载
概念:C++中,在同一个作用域下面,函数名相同,参数列表不同,即构成重载
前提条件: 在同一个作用域下面 平等关系 (如果作用域不同,不是重载)
函数名相同 一模一样
参数列表不同:
1.参数类型不同(对应位置)
2.参数个数不同
3.常属性不同 是指 指针or引用类型的常属性不一样
与函数返回值类型无关
C++中,在调用重载的函数时,编译阶段根据函数调用时所传递参数的个数和类型来决定调用重载中哪一个函数
静态运行时:在编译阶段确定调用哪个函数
动态运行时: 在运行阶段才确定调用哪个函数
C++中为什么可以支持重载?
g++编译器会对程序中的函数名进行换名操作,
将参数列表中的类型信息汇合到函数名中,以保证函数名的唯一
extern "C",要求g++编译器不要对函数进行C++的换名操作,
只对一个函数起作用,如果要全部,需要用{}包含
以方便在C语言中调用C++编译生成的代码
重载时函数调用匹配问题:
1.先进行精确匹配,参数个数和类型完全一样
2.有常属性的指针和引用 可以和 没有常属性的指针和引用 的函数构造重载
如果非const版本和const都有,那么在调用时根据常属性进行匹配调用
如果只一个版本的函数,没有常属性的数据 可以调用 const版本
但是const属性的一定是不能调用非const版本的函数
自动加强const属性 但是不能贸然丢失const属性
3.如果在匹配过程中,没有完全的匹配,会对参数进行隐式转换
如果隐式转换的过程中,发现有两个或者以上的函数,都能够进行相等的转化,就会产生歧义
func(char,char)
func(int,int)
func('b',44);
2.支持默认值(缺省值)
在定义函数时,可以给函数参数以默认值(相当于赋值语句一样),那么在调用该函数时,
(1)可以传递该位置的参数,也可以不传递(取缺省值)
(2)如果某个函数的一个参数有缺省值,那么该参数后面所有的参数都必须要有缺省值
缺省值靠右原则
(3)如果函数的声明和定义分开的情况下,那么缺省参数只能放在声明中!!
(4)要注意和重载产生歧义
3.支持哑元
函数的参数只有类型,没有名字的形参,谓之哑元
在重载++、--运算符时会使用哑元
注意重载产生重定义
4.支持内联
C++ inline声明的函数
(1)在调用内联函数时,用函数的二进制指令代码替换掉函数调用指令,减少函数调用的时间开销
把这种策略称为内联
(2)inline关键字,只是建议编译器将指定的函数编译成内联,但仅仅是建议而已
(3)频繁调用的简单函数适合内联,而稀少调用的复杂函数不适合内联
(4)递归函数无法内联
C语言函数的隐式声明,C++没有隐式声明,C++中调用的函数必须先声明
C++中如果声明函数的参数为空(啥也没写),就代表了C语言中void的作用
C++中不能在调用无参函数时传递实参
动态内存
C动态内存: malloc/calloc/realloc/free sbrk/brk mmap/munmap
C++动态内存: new/delete 底层调用的是C语言的函数
new/delete 用来对单个变量内存的申请/释放
数据类型* 指针变量 = new 数据类型;
数据类型* 指针变量 = new 数据类型(初始值);
delete 指针变量;
new[]/delete[] 用来对数组进行内存申请/释放
new/delete和malloc/free的区别?
(1)new/delete 是C++操作符 malloc/free是C语言标准库的函数
(2)new在申请动态内存时不需要自己计算内存大小
malloc指定申请动态内存的字节数
(3)new会调用类的构造函数 malloc不会
new申请的动态内存不会有垃圾值
delete在释放动态内存时 会调用类的析构函数 但free不会
(4)malloc出错 返回NULL并且设置errno
new出错 抛出异常
(5)malloc返回值类型为 void*
new返回值类型 为 new的数据类型的指针
(6)new数组内存时需要用 new[]
malloc可以直接申请数组内存
引用
引用即别名
数据类型& 变量名 = 目标对象;
注意事项:
引用必须直接初始化 形参列表引用没有直接初始化(在函数调用时初始化)
引用一旦初始化 不能更改引用对象(不能指向其他对象) 从一而终
对引用对象的操作,直接影响其目标对象
int a = 10;
int& r1 = a,r2;
int* p1,p2;
常引用 int& r = 10;
引用的应用:
(1)函数参数 在函数内部可以直接修改实参的值
(2)函数的返回值 需要确保在函数调用之后,返回的对象依然有效
能够返回全局变量 静态变量 动态内存对象 引用参数的的引用
不能返回局部变量的引用 编译警告 引用的对象的值不确定 行为不确定
(3)多态 父类引用类型引用子类对象
引用与指针:
(1)引用的本质就是指针,C++对指针的一个封装 很多场合引用和指针可以互换
(2)C++层面 上引用和指针有以下方面的不同
A.指针是实体变量,但是引用不是实体变量
sizeof(指针) == 4/8 当平台和编译器确定,大小唯一
int a=1; int& ra=a; sizeof(ra)== 4
double d=1.0; double& rd=d; sizeof(rd) ==8
B.指针可以不初始化 但是引用必须初始化
int *p;
int& r;
C.指针可以修改目标(指向不同的目标) 但是引用不能修改引用方向
int *p = &a;
p = &b;
int& r = a;
r = b;
D.可以声明void指针 void* 但不能声明void引用 void&
void* p;
void& r = v;
E.可以定义指针的指针(二级指针) 但不可以定义引用的引用
int **p;
int a; int &r = a;
int&& rra = r;
int& rra = r;
F.可以定义指针的引用 但不可以定义引用的指针
int *p;
int*& rp = p;
int& *pr;
G.可以定义数组的引用 但不能定义引用的数组
数组的引用 本质是引用,引用了一个数组
int arr[5] = {1,2,3,1023,1024};
int (&rar)[5] = arr;
int a,b,c,d,e;
int& brr[5] = {a,b,c,d,e};
类型转换
隐式类型转换 自动类型转换
C++中,int* ----> void* 隐式类型转换
void* ---> int* 不能隐式类型转换
基础数据类型之间都可以进行隐式类型转换
强制类型转换 (int)3.14
(目标类型)(源对象)
C++提供了四种显示类型转换运算符
1.静态类型转换
static_cast<目标类型>(源对象)
如果目标类型和源对象类型在某一个方向上可以进行隐式类型转换,
那么在两个方向上都可以进行静态类型转换;
反之,如果在两个方向上都不能进行隐式类型转换,
则任意一个方向上都不能进行静态类型转换
类类型定义自定义转换规则,也可以使用静态类型转换
2.去常属性类型转换
const_cast<目标类型>(源对象)
只能去除指针(成员指针)和引用的常属性
C语言中,局部变量用const修饰,表示只读,可以通过指针进行修改该变量
C++中,局部变量用const修饰,那么该变量是常量 在编译时会直接用该值直接替换
3.动态类型转换
dynamic_cast<目标类型>(源对象)
用在具有多态性的父子类指针或引用之间
4.重解释类型转换
reinterpret_cast<目标类型>(源对象)
用在指针与指针之间 或者 指针与整数之间 做类型转换
C++指针类型转换:
1.指针 可以隐式转换成 void*
2.void * 可以用static_cast 静态类型转换成其它类型的指针
3.不同类型的指针可以用 reinterpret_cast 重解释类型转换
4.强制类型转换
5.多态性的父子类型指针之间可以用 dynamic_cast 动态类型转换
面向对象编程
类和对象
类: 是一类事物的总称,是一个泛化的概念 定义的类型
对象: 是类实例化的结果,特指一个事实存在的事物 实例化的变量
类是对一类对象的抽象化的描述,对象是类实例化的结果
类是对象一类事物的描述,把这类事物共同拥有的特征抽象为属性
把这类事物共同拥有的行为抽象为方法 ----- 即类的抽象过程
如果对属性和方法加以访问控制属性的限制 --- 类的封装
面向对象的三大特征: 封装、继承、多态
类里面的成员方法可以直接访问成员变量
对比C语言中的结构体和函数,C++可以在结构体中定义函数,这些函数可以直接访问成员变量
C语言中调用函数,把结构体变量作为参数传入到函数中
C++中的成员函数可以直接访问成员函数,如何调用成员函数?
对象.成员方法(实参) --- 结构体变量不需要作为参数传递给函数
面向对象编程里,把成员函数称为方法(过程),把成员变量称为属性
C++中为了体现和C语言的不一样,用class来替代struct,为了兼容C,所以保留了struct
C++中封装类用class关键字
封装类的语法:
class 类名{
访问控制属性:
成员属性;
访问控制属性:
成员方法;
};
C++中struct和class的区别:
struct中默认访问控制属性是 public 公开的
class中默认的访问控制属性是 private 私有的
公开的属性可以直接用 = {}形式初始化
非public不能用={}的形式初始化
访问控制属性:
public: 公开的 任何地方都可以通过对象进行访问
protected: 保护的 只有本类和该类的子类中才可以访问
private: 私有的 只有在本类中才能够访问
C++的类里面:
可以定义变量 属性 private
可以定义函数 方法 public
属性和方法必须通过 对象. 的方式进行访问和调用
在成员方法中可以直接访问成员变量 访问的是哪个对象的成员属性取决于哪个对象调用
构造函数和方法
构造函数:
类名(形参列表){
}
特点:
1.没有返回值类型 也不能为void
2.函数名 == 类名
什么时候调用:
在实例化对象时自动调用
实例化对象一定会调用构造函数
C++编译器在编译时:
如果发现一个类没有提供构造函数,那么编译器会自动生成一个无参构造函数
如果发现一个类有提供构造函数,那么编译译将不会再提供默认的无参构造函数
构造函数也可以重载,即支持按多种方式进行构造对象
实例化对象
实例化对象:分配内存 构造函数
意味着一定有调用构造函数
一.在栈区实例化对象
1.调用无参构造函数实例化对象 类型名 --- class/struct类型
类型名 对象名; Emp e1;
类型名 标识符();
类型名();
2.调用有参构造函数
类型名 对象名(实参列表); Emp e3(110,"jack","saller",1.001)
类型名(实参列表);
匿名对象:
没有名字的对象,直接用类型名(实参列表);来构造
经常在函数中会返回匿名对象 或者 在调用函数时 传递匿名对象
往往会直接用变量(形参)去接收匿名对象
这个过程会进行优化 直接让变量"引用"该匿名对象
如果不优化:1.调用拷贝构造 2.调用拷贝赋值函数 拷贝过程
类型名 对象名 = 匿名对象;
二、在堆区实例化对象
类型名 *指针变量 = new 类型;
类型名 *指针变量 = new 类型();
类型名 *指针变量 = new 类型名(实参列表);
注意:
类型名 *指针变量 = (类型名*)malloc(sizeof(类型名));
没有实例化对象 没有调用构造函数 只分配了内存空间
普通对象访问成员属性和调用成员函数用 .
类指针访问成员属性和调用成员函数时用 -> (*指针变量).
new 类型名(实参列表);
数组对象:
定义类类型成员的数组
类型名 数组名[数组长度];
类型名 数组名[数组长度] = {类型(实参列表),...};
类型名 *指针 = (类型*)malloc(sizeof(类型)*len);
类型名 *指针 = new 类型[数组长度];
C++11支持
类型名 *指针 = new 类型[数组长度]{类型(实参列表),...};
构造函数中缺省参数
this指针
在成员函数中,可以直接访问成员属性 和 调用 成员方法
在类外面调用成员方法
对象.成员方法名(实参列表);
在所有的成员函数(构造函数)中,都隐含了一个参数this, this是一个该类类型的指针
对象.成员方法名(实参列表); ---> 成员方法名换名之后(&对象,实参列表)
成员方法名换名之后(类型 *this,...)
在构造函数中,this指针指向正在被构造的对象
在普通成员函数中,this指针指向正在调用该成员方法的对象
如果形参列表(局部变量)和成员变量同名的
在函数中直接访问,局部优先原则,直接访问到的是局部变量
同名的局部变量隐藏了成员变量
可以使用 this->成员变量 的形式 来指明 访问的是成员变量 而不是局部变量
this有一个作用,区分同名的局部变量和成员变量
成员函数中返回当前对象 return *this;
初始化列表
构造函数的功能: 给成员变量进行初始化工作
只有构造函数中才有初始化列表
class 类名{
类名(形参列表):初始化列表{
}
};
初始化列表简化了对成员属性的赋值
初始化列表:成员属性名(形参/成员属性名/new) 多个之间用逗号隔开
在初始化列表中,能够区分同名的局部变量和成员变量,
在小括号()外面,默认为成员属性
如果数组作为成员属性,不能用初始化列表的形式进行初始化
this->成员属性 = 形参;
初始化列表的执行顺序与成员变量定义的顺序相关:
初始化列表的执行顺序 和 初始化列表的代码先后无关,只与成员变量定义的顺序有关
先定义的成员变量会先执行初始化列表中的语句,后定义后执行
const成员属性:
必须在初始化列表中进行初始化
除了初始化列表中,在其它地方进行"初始化",都是对const成员属性进行修改(赋值)
引用类型属性必须在初始化列表中进行初始化
类类型(class/struct)成员:
如果一个类定义了类类型成员,则默认生成构造函数,还是自定义的构造函数
如果在初始化列表中,没有对类类型成员进行显示地调用构造函数进行初始,
则默认会在初始化列表中,调用类类型成员的无参构造函数
如果该类类型成员在类中并无参构造函数,则如果在实例化对象时会报错
类类型成员在初始化列表中初始化: 成员名(实参列表)
构造函数:
在初始化列表中,按照继承顺序依次调用父类的构造函数
然后按照类类型成员属性定义的先后顺序,依次调用成员属性的构造函数
默认都是调用无参构造函数
可以指明调用有参构造函数
常对象 和 常方法
const修饰的对象称为常对象
常对象只能调用常函数(方法)
在类里面,常版本的方法和非常版本的方法构造重载
常版本的方法
class 类名{
....
返回值类型 成员方法名(形参列表)const{
}
void func(void){}
void func(void)const{}
对象.func() --> func(&对象)
常方法(函数)const修饰的是*this,即调用该方法的那个对象
普通对象(非常对象)在调用方法时首先是普通方法,
如果普通方法不存在时,也可以调用非方法
只有成员函数才可以是常函数
常对象的属性能不能修改?
mutable修饰成员属性,表示即使是常对象,也可以修改该成员属性
静态属性 和 静态方法
静态属性 static修饰的属性 称为静态属性 类属性
静态属性属于类本身 而不属性某个对象 该类的所有对象共享一份
普通的成员属性 每一个对象都有独立的一份
静态的属性 所有对象共享一份 相当于加了类访问作用域的全局变量
静态属性可以直接 类名::静态属性 的方式进行访问
也可以通过 对象.静态属性 的方式进行访问
静态属性 必须在类外初始化
静态属性的大小不包含在对象和类进行sizeof里面的
线程安全的容器 进行操作时必须加锁
静态方法 static修饰的成员方法 称为类方法
相当于加了 类名:: 作用哉限定符的 全局函数
*静态方法中没有隐含的this指针,不能直接访问成员属性和调用成员函数
在静态方法只能访问静态属性 和 调用其它的静态方法