01.c++基础

文章目录

一、第一个C++程序

1 编译方式
  • 1)gcc xx.cpp -lstdc++

    tarena@ubuntu:~/me/C++/day01$ gcc first01.cpp -lstdc++

    @ubuntu:~/me/C++/day01$ a.out

    hello world!

    2)g++ xx.cpp //推荐

    tarena@ubuntu:~/me/C++/day01$ g++ first01.cpp

    tarena@ubuntu:~/me/C++/day01$ a.out

    hello world!

2 文件扩展名
  • 1) .cpp //推荐

    2) .cxx

    3) .cc

    4) .C

3 头文件
  • #include<iostream>

    --> C++中和I/O相关的类型,对象,函数都在该头文件中

    --> C++中绝大多数头文件没有.h后缀

    --> C++开发中,可以直接使用c语言头文件中,同时还提供了一套不带.h的替换版本

    eg:

    #include <stdio.h> ==> #include<cstdio>

    #include <stdlib.h> ==> #include<cstdlib>

    #include <string.h> ==> #include<cstring>

4 标准的输入输出
  • 1) 使用cin对象表示标准输入//类似scanf()

    int i;

    scanf("%d",&i);//C

    cin >> i; //C++

    -------------------------------------------------------

    int i,double d;

    scanf("%d%lf",&i,&d);//c

    cin >> i >> d;//c++

    注:">>"输入操作符

     

    2) 用cout对象表示标准输出//类似printf()

    int i=123;

    printf("%d\n",i);//C

    count << i << endl;//C++

    注:"<<"输出操作符

    --------------------------------------

    int i = 123,double d = 4.56;

    printf("%d,%lf\n",i,d);//C

    cout << i <<','<< d << endl;//C++

二、名字空间(命名空间) 改变作用域

1 功能:
  • 1) 避免名字冲突

    2) 划分逻辑单元

2 定义名字空间
  • namespace 空间名{

    名字空间成员1;

    名字空间成员2;

    ...............

    }

    注:名字空间成员可以是全局函数,全局变量,自定义类型,名字空间.

3 名字空间成员使用
  • 1)通过作用域限定操作符"::"

    空间名::要访问成员;

    eg:

    "std::cout" 表示使用标准名字空间里面cout

    2)名字空间指令

    using namespace 空间名;

    在该条指令以后的代码中,指定名字空间成员都可以见,可以直接访问,省略"空间名::"

    3)名字空间声明

    using 空间名::名字空间成员;

    将名字空间中特定一个成员引入到声明所在的作用域中,在该作用域访问这个成员就如同是访问自己的成员一样,可以直接访问,省略"空间::"

4 全局作用域和无名(匿名)名字空间 //了解
  • 1) 没有放在任何名字空间的成员,属于全局作用域,可以直接访问,如果和局部作用域的成员名字一样,局部优先;这时如果还希望访问到全局作用域的成员

    可以通过"::成员"形式来显式指明

    2) 定义名字空间时,可以没有名字,即为无名名字空间,对于无名空间里面的成员和全局作用域的成员一样,也可以直接访问,只是被局限在当前文件中

    全局可在整个程序内使用

    匿名名字空间只可以在本文件中使用

    ::变量名或函数名,是访问全局变量和全局函数的方法.

5 名字空间嵌套//了解
    eg:
        namespace ns1{
            int num = 10;
            namespace ns2{
                int num = 20;
                namespace ns3{
                    int num = 30;
                }
            }
        }
        cout << ns1::num << endl;
        cout << ns1::ns2::num << endl;
        cout << ns1::ns2::ns3::num << endl;

三、C++的结构体,联合体,枚举

1 结构体
  • 1)当定义结构体变量时可以省略struct关键字

    2)在结构体内部可以直接定义函数,称为成员函数(方法),成员函数中可以直接访问当前结构体中的其它成员

2 联合体(union)//了解
  • 1)当定义联合体变量时可以省略union关键字

    2)支持匿名联合

3 枚举
  • 1)当定义枚举变量时,可以省略枚举关键字.

    2)C++中枚举被看做是独立的数据类型,而c语言中枚举本质就是整型数.

    enum STATE{SLEEP,RUN,STOP};
    STATE s;
    s = STOP;//C:ok,C++:ok
    s = 2;//C:ok,C++:error
  • 枚举可以当做返回类型.

四、C++的字符串

1 回顾c中字符串
  • 1) 字面值常量 "hello"

    2) 字符指针 char*

    3) 字符数组 char[]

eg:
	char* p1 = "abcdef";
	char* p2 = "123456";
	strcpy(p1,p2);//段错误
	段错误信号 11
	---------------------------
	char arr1[] = "hello";
	char arr2[] = "wangjianli";
	strcpy(arr1,arr2);//内存越界,结果未知,危险!
	--------------------------------
	char arr[] = "hello";
	char *p="world";
	p = arr;//OK
	arr = p;//error
	---------------------------------------
2 C++可以完全兼容c中字符串表示方式,同时增加了string类型转换表示字符串.
  • 1)字符串的定义
 	string s;//定义空字符串
 	string s = "xxxx"; //定义同时初始化
 	----------------------------------------------------
 	//下面两种写法实际初始化和上面相同
 	string s("xx");
 	string s = string("xx");
 	
 	//char*类型指针,指向了一块在堆区动态分配的内存,这块内存存放了字符串的值.
 	namespace std{
 		struct string{
 			char *;
 		}
 	}
  • 1) 字符串拷贝: =
	string s1 = "hello";
	string s2;
	s2 = s1;//拷贝
  • 2) 字符串的连接: + +=
	string s1 ="abc";
	string s2 = s1 + "def";
	cout << s2 <<endl;//"abcdef"
  • 3) 字符串比较: == != > < >= <=
	if(s1 == s2){.....}
  • 4) 随机访问: []
	string s ="hello";
	s[0] = 'H';
  • 5) 成员函数
	size()/length();//获取字符串长度	
	string str = "xxx";
	str.size();
	str.length();
	
	c_str();//获取c风格的字符串(const char *)
	string s1 ="xxx";
	const char* s2 = "xxx";
	s1 = s2;//OK
	s2 = s1.c_str();//ok
	void cfunc(const char* str){}
	cfunc(str);

五、C++的布尔类型: bool

  • 1 bool类型是C++中基本数据类型,专门表示逻辑值,逻辑值真为true,逻辑假为false

    2 bool类型在内存占一个字节:1表示true,0表示false

    3 bool类型变量可以接收任何类型表达式的结果,数值非零则为真,为零则为假.

六、操作符别名 //了解

	&& <==> and
	|| <==> or
	^  <==> xor
	{  <==> <% 
	}  <==> %>
	...

七、C++的函数

1 函数重载(overload)
  • 1)定义

    在相同作用域中,可定义同名函数,但是它们的形参必须有所区分(参数个数,参数类型,和形参变量名无关),这样的函数关系称为函数重载.

    注:函数重载和返回类型无关

    eg:图形库中绘图函数

	void drawRect(int x,int y,int w,int h){....}
	void drawCircle(int x,int y,int r){....}
	......
	--------------------------------------------------------
	void draw(int x,int y,int w,int h){...}
	void draw(int x,int y,int r){...}
	......
  •  

    2)函数重载匹配

    • 调用函数时,编译器根据实参和形参匹配程度,自动选择最好的重载版本.

      当前g++编译器匹配的优先级:(优先级以编译器的结果为准)

      完全匹配>=常量转换>升级转换>降级转换>省略号匹配

	char* > const char *
	char = const char
  • 当通过函数指针调用重载关系的函数时,根据指针类型选择匹配的重载版本.(函数指针在定义时已经匹配完成,函数指针是什么类型,调用什么类型的函数)
  •  

    3)函数重载的原理 (函数的重入是什么????)

    nm x.0

    g++ 在编译函数,会进行函数换名

    void func(int i,double j){}

    _Z4funcid 4是函数名字符数 i 形参类型int d 形参类型double

    C++的编译器是通过函数换名,将参数表的类型信息整合到新的函数名中,实现解决函数重载和名字冲突的矛盾.

    (笔试题):C++中extern "C"声明作用?

    可以在函数声明前面加入 extern "C" ,要求C++编译器不要对该函数进行换名,便于C程序直接调用.

    注:被extern "C"声明的函数无法被重载.

    --------------------------------------------

    vi -O 用竖屏方式打开多个文件

    vi -o 用横屏方式打开

    vs

    sp

    Ctrl + W + W 进行换屏

 

2 函数的哑元参数 (C语言直接报错)
  • 1)定义

    定义函数时,只有类型而没有变量名形参被称为哑元.

    void func(int /*哑元*/) {...}

    2)使用哑元的场景

    -->操作符重载,通过哑元区分前后++/--(后面讲)

    -->兼容旧代码

    eg:
	//算法库:
	void math_func(int a,int b){...}
	//使用者:
	int main(void){
		math_func(10,20);
		...
		math_func(30,40);
	}
	--------------------------------------------
	//算法库升级
	//哑元作为一个显示标记,告诉程序员这个参数没用
	void math_func(int a,int /*哑元*/){...}
	//使用者:
	int main(void){
		math_func(10,20);
		...
		math_func(30,40);
	}

 

3 函数的缺省参数(默认实参)
  • 1) 可以为函数的参数指定缺省值,调用该函数时,如果不给实参,就会取缺省值作为默认实参.

	void func(int a,int b=0/*缺省参数*/){...}
  • 2) 靠右原则

    如果函数的某个参数有缺省值,那么该参数右侧的所有参数都必须带有缺省值.

    3) 如果函数声明和定义分开写,缺省参数应该写在函数声明部分,而定义部分不写(加注释标明);

	void func(...);//函数声明
	void func(...){...}//函数定义
  • 4) 不要和函数重载形成歧义
	void func(int a,int b=20,int c=30);
	void func(int a);
	func(10);

 

4 内联函数(inline) (笔试题)
  • 1) 使用inline关键字修饰的函数即为内联函数,编译器将会 尝试 进行内联优化,可以避免函数的调用开销,提高代码的执行效率.

    开销:保护现场,恢复现场......增加执行时间.

	inline void func(void) {...}
  • 内联关键字对程序的实现没有影响,影响的是编译器的执行.

    内联是在编译时直接用函数机器指令替换调用语句的机器指令,避免调用的跳出跳入的时间耗费.(以空间换时间)

     

    2) 使用说明

    --> 多次调用的小而简单的函数适合内联函数

    --> 调用次数极少或大而复杂的函数不适合内联

    --> 递归函数不能内联(递归函数是否结束依赖于上一次的执行结果,执行次数不确定)

    --> 虚函数不能内联(后面讲)

    • 注: 内联优化只是一种建议,而不是强制要求,对于一个函数能否内联优化主要取决于编译器,有些函数不加inline修饰也会默认处理为内联优化;

      有些函数即便加了inline修饰也会被编译器忽略掉.

     

八、C++的动态内存管理(什么是内存泄露,有什么危害)

1 回顾C语言中的动态内存管理
  • 1)分配:

    malloc()(calloc/realloc/brk/sbrk/mmap)
  • 2)释放:
    free()

 

2 C++的动态内存管理
  • 1) 分配:

    new,new[]
  • 2) 释放:
    delete,delete[]
  • 内存分配时可以同时初始化 new int(200) 将存储区的值初始化为200

new[]分配,必须用delete[]释放,否则会发生内存泄露.

-std=c++11 使用C++11语法

笔试题(new,delete和malloc,free的区别)

进程: 栈段,堆段,数据段(全局区),代码段

 

九、C++的引用(Reference) (重点)

1 定义
  • 1) 引用即别名,就是某个变量的别名,对别名操作和对变量本身操作完全相同.

    2) 语法

    类型 & 引用名 = 变量名;

    注: 定义引用时必须初始化,初始化以后绑定的目标变量不能再修改.

    注: 引用的类型和绑定的目标变量类型一致

    eg:

	int a = 100;
	int &b = a;//b就是a的别名

 

2 常引用
  • 1) 定义引用时加const修饰,即为常引用,不能通过常引用修改目标变量.(只读访问)

	const 类型 & 引用名 = 变量名;
	类型  const & 引用名 = 变量名; //等价
	
	const int *p;
	int const *p;//等价
	
	int * const p;指针常量
	
	int a=100;
	const int &b=a;//b就是a的常引用
	b++;//error
  • 2) 普通的引用也可以称为左值引用,只能引用左值不能引用右值;而常引用也可以称为万能引用,既可以引用左值也可以引用右值.

    注:关于左值和右值

    左值(lvalue):可以放在赋值操作符的左侧,可以被修改,是左值

    右值(rvalue):只能放在赋值操作符的右侧,不能被修改,是右值

 

3 引用型函数参数
  • 1) 可以将引用用于函数参数,这是形参就是实参的别名,可以通过形参直接修改实参,

    同时传递引用型参数可以避免参数值传递的过程,减小函数调用时间的开销,

    提高代码执行效率,主要对自定义结构体类型参数效率提高效果显著.

2) 引用型参数有时可能会意外的修改实参值,

如果传递引用型参数仅是为了提高传参效率,不想修改实参值,

可以附加const修饰,将参数定义为常引用,提高传参效率的同时还可以接收常量型的实参。

 

4 引用型函数返回值
  • 1)可以将 引用用于函数的返回值,这时函数返回结果就是return后面数据的别名,可以避免返回值的开销,提高代码的效率.

    2)如果函数返回数值是左值引用,那么函数调用表达式结果也是左值.

    注:不要从函数中返回自动局部变量的引用,因为所引用的目标变量内存会在函数返回以后被释放,使用危险!

    可以从函数中返回成员变量,静态变量,全局变量的引用。

 

5.引用和指针有何区别联系(笔试题)
  • 1)从c语言(汇编)的角度看待引用,其本质就是指针,但是C++开发中推荐使用引用而不是指针.

	int i = 100;
	int *pi = &i;
	int &ri = i;
	*pi <==> ri;
  • 引用不占内存,指针占用内存空间.

    2) 指针定义是可以不做初始化,指针的目标也可以修改(指针常量除外);而引用在定义时必须初始化,而且初始化以后其目标不能再修改.

	int a = 3,b = 5;
	int *p;//ok
	p = &a;//p指向a的地址
	p = &n;//p指向b的地址
	----------------------------------
	int &r;//error
	int &r = a;
	r = b;//不是修改目标,仅是赋值操作
  • //往下了解:

    3) 可以定义指针的指针(二级指针),但是不能定义引用的指针;

	int a = 10;
	int *p = &a;
	int **pp = &p;//ok,二级指针
	--------------------------
	int &r = a;
	int &*pr = &r;//error,引用的指针
	int *pr	= &r;//ok,但指针是指向a的指针.
  • 4) 可以定义指针的引用(指针变量的别名),但是不能定义引用的引用;

	int a = 10;
	int *p = &a;
	int *&rp = p;//ok,指针的引用(指针的引用)
	------------------------------------------------------
	int &r = a;
	int &&rr = r;//error,引用的引用  (C++11中叫右值引用)
	int &rr = r; //ok,但就是普通引用,相当于还是给a取别名
  • 5) 可以定义指针数组,但是不能定义引用数组,可以定义数组引用(数组的别名)

	int a=10,b=20,c=30;
	int *parr[3]={&a,&b,&c}; //ok,指针数组
	int & rarr[3] = {a,b,c}; //error,引用数组
	---------------------------------------------
	int arr[3] = {a,b,c};
	int (&rarr)[3] = arr;//ok,数组引用
  • 6) 可以定义函数指针,也可以定义函数引用(函数的别名),语法特性一致

	void func(int i,double d) {...}
	int main(void){
		void (*pfunc)(int,double) = func; //函数指针
		void (*rfunc)(int,double) = func; //函数引用
		pfunc(1,1.23);
		rfunc(1,1.23);
		return 0;
	}

 

十、类型转换

 

1 隐式类型转换
	char c = 'q';
	int i = c; //隐式类型转换
	-----------------------------------
	void func(int i){}
	func(c);//隐式
	---------------------------------
	int func(void){
		return c;//隐式
	}

 

2 显示类型转换
  • 1) C++中兼容C中强制转换.(隐式转换降低可读性)

	char c = 'q';
	int i = (int)c;//C风格
	int i = int(c);//C++风格,本质和上面一样

 

  • 2)C++扩展的四种操作符形式的显示转换 编译器可以进行安全检查

    -->静态类型转换

    语法:

    目标变量 = static_cast<目标类型>(源类型变量)

    主要适用场景:

    用于将void *转换为其他类型指针.

    eg:

	char c = 'q';
	int i = static_cast<int>(c);
  • -->动态类型转换(后面讲)

    语法:

    目标变量 = dynamic_cast<目标类型>(源类型变量)

    主要适用场景:

-->常类型转换

语法:

目标变量 = const_cast<目标类型>(源类型变量)

主要适用场景:

用于除去指针或引用的常属性.

	const int ci = 10;
	int *pci = const_cast<int*>(&ci);
  • 编译优化导致 ci 和 *pci的值可能不同:

    为了提高效率,ci中的在第一次使用,ci的值会放在CPU的寄存器中,每次读取直接从CPU的寄存器中取值,而不从内存取值

-->重解释类型转换

语法:

目标变量 = reinterpret_cast<目标类型>(源类型变量)

适用场景:

->在指针和整型数之间进行转换

->任意类型指针或引用之间进行转换

	eg:向物理内存地址0x12345678,存放数据"123"
	int *paddr=reinterpret_cast<int*>(0x12346578);
	int *paddr=(int *)0x12345678 
	*paddr = 123;
  •  

    小结: 来自C++社区给C程序员建议

    1 慎用宏,可以使用const,enum,inline

	#define PAI 3.14
		--> const double PAI = 3.14;
	#define SLEEP 0;
	#define RUN 1;
	#define STOP 2;
		--> enum STATE {SLEEP,RUN,STOP};	
	#define Max(a,b) ((a)>(b)?(a):(b))
		--> inline int max(int a,int b){
			return a > b ? a : b; 
		}
  • 2 变量随用随声明同时初始化

3 尽量使用new/delete取代malloc/free;

4 少用void* , 指针计算 , 联合体 和 强制转换

5 尽量使用string表示字符串,少用c风格char*/ char[]

十一、类和对象 //了解

 

1 什么是对象
  • 万物皆对象,任何一种事物都可以看做是对象.
2 如何描述对象
  • 通过对象的属性(名词,数量词,形容词)和行为(动词)来描述和表达对象.
3 面向对象程序设计
  • 对自然世界中对象的观察引入到编程实践的一种理念和方法,这种方法被称为"数据抽象",即描述对象时把细节的东西剥离出去,只考虑有规律性,一般性,统一性的东西.
4 类
  • 类是将多个对象共性提取出来定义的一种新的数据类型,是对 对象属性和行为的抽象描述.

    现实世界      类       虚拟对象

    具体对象---抽象--->属性/行为---实例化-->具体对象

 

十二、类的定义和实例化

 

1 类的一般语法形式
	struct/class 类名:继承方式 基类 {
	访问控制限定符:
		类名(参数表):初始化列表{...} //构造函数
		~类名(void){...} //析构函数
		返回类型 函数名(参数表){...} //成员函数
		数据类型 变量;//成员变量
	};

 

2 访问控制限定符
  • 1)public: 公有成员, 任何位置都可以访问

    2)private: 私有成员, 只有类中自己的成员函数可以访问

    3)protected : 保护成员(后面讲)

    注: struct 定义类,类中成员的缺省访问控制属性是public,而如果使用class定义类,类中成员的缺省访问控制属性是private.

eg:
	struct/class XX{
		int a; //公有成员(struct)/私有成员(class)
	private:
		int b; //私有成员
	public:
		int c; //公有成员
		int d; //公有成员
	private:
		int e; //私有成员
	};
3 构造函数(Constructor)
  • 1) 语法
	class 类名{
		类名(形参表){
			//主要负责初始化对象,即初始化成员变量
			
		}
	};
  • 2) 函数名和类名相同,没有返回类型

    3) 构造函数在创建对象时自动被调用,不能像普通函数显示的调用

    4) 在每个对象的生命周期,构造函数一定会被调用,但仅被调用一次.

练习: 实现一个电子时间类,要求使用构造函数初始化时钟为当前的系统时间,并以秒为单位运行.

提示:

	class Clock{
	punlic:
		构造函数(time_t )
	private:
		int 时,分.秒
	};
	
	Clock clock(time(NULL));
4 对象的创建和销毁
  • 1) 在栈区创建单个对象

    类名 对象名(构造实参); //直接初始化

    类名 对象名=类名(构造实参); // 拷贝初始化,实际等价 类名(构造实参) 匿名对象,临时对象

    类名 对象名 = 构造实参; // 仅适用于只有一个构造实参的时候使用,拷贝初始化的一种简化

	string s("xx");
	string s = string("xx");
	string s = "xxx";
  • 2) 在栈区创建多个对象(对象数组)

    类名 对象数组[元素个数] = {类名(构造实参),...};

3) 在堆区创建单个对象

类名 *对象指针 = new 类名(构造实参);

int *pi = new int(200);

delete 对象指针; //销毁堆区创建的单个对象

4) 在堆区创建/销毁多个对象

类名 *对象指针 = new 类名[元素个数] {类名(构造实参),...};

int *parr = new int[10]{1,2,3,...};

delete[] 对象指针;

注:使用new操作符首先会分配内存,然后自动调用构造函数完成对象的初始化操作; 而如果使用malloc仅能实现内存分配,不会调用构造函数.

5 多文件编程
  • 1) 类声明放在xx.h(头文件)中

    2) 类的实现放在xx.cpp(源文件)中

十三 构造函数和初始化列表

1 构造函数可以重载,也可以带有缺省参数
  string();   string s;
  string( const string& s );  string s1 = string(s);
  string( const char* str );  string s2 = string("hello");
2 缺省构造函数(无参构造函数)
  • 1)如果类中没有定义任何构造函数,编译器会提供一个缺省的无参构造函数:

    -->对于基本类的成员变量不做初始化

    -->对于类 类型的成员变量(成员子对象),将会自动调用相应类的无参构造函数来初始化.

    2)如果自己定义了构造函数,无论是否有参数,编译器都不会在提供缺省无参构造函数了.

3 类型转换构造函数(单参构造函数)
	class 类名{
		[explicit] 类名(源类型) {...}
	};
  • 可以实现将源类型变量转换为当前类类型对象.

    注:可以使用explicit关键字修饰类型转换构造函数,可以强制这种转换必须要显示的完成.

4 拷贝构造函数(复制构造函数)
  • 1) 用已存在的对象,作为同类型对象的构造实参,创建新的副本对象,将调用该类的拷贝构造函数.
	class 类名{
		类型(const 类名&){...}
	};
  	class A{...};
  	A a1;
  	A a2(a1);// A a2 = a1;  A a2 = A(a1); 拷贝构造
	
	A a2;
	a2 = a1; // 拷贝赋值
  • 2) 如果类中没有定义拷贝构造函数,那么编译器会为该类提供一个缺省的拷贝构造函数;

    --> 对于基本类型成员变量,按字节复制

    --> 对于类类型成员变量(成员子对象), 自动调用相应类的拷贝构造函数来初始化.

注:一般不需要自定义拷贝构造函数,因为缺省的拷贝构造函数已经很好用了.

3) 关于拷贝构造函数的调用时机

--> 用已定义对象作为同类型对象的构造实参

--> 以对象的形式向函数传递参数

--> 从函数中返回对象(有时会被编译器优化)

g++ cpCons08.cpp -fno-elide-constructors

去掉和构造函数相关的编译器优化

5 初始化列表
  • 1) 语法
	class 类名{
		类名(参数表):成员变量(初值),成员变量(初值)...{
			函数体;
		}
	};
	int i;
	i = 0;
	-------------------
	int i = 0;
eg:
	//先定义成员变量,在赋初值
	Student(const string &name,int age,int no){
          cout << "构造函数" << endl;
          m_name = name;
          m_age = age;
          m_no = no;
    }
    //定义成员变量同时初始化
    Student(const string &name,int age,int no)
    	:m_name(name),m_age(age),m_no(no){}
  • 2) 多数情况使用初始化列表和在构造函数体赋初值没有太大区别,两者可以任选;

    但是有特殊场景,必须要使用初始化列表;

    比如,类中包含了类类型的成员变量(成员子对象),而该类没有无参缺省构造函数,或希望以有参的方式来初始化该成员子对象,则必须使用初始化列表.

3)类中有"const"或"引用"成员变量,必须使用初始化列表.

 

十四 this指针和常成员函数

1 this指针
  • 1) 类中的成员函数(包含构造函数,析构函数)都隐藏一个当前类类型的指针参数,名为this,在成员函数中访问类中的其他成员,本质都是this指针来实现的.
	class A{
	public:
		int m_i;
		//void print(A *this)
		void print(void){
			//cout << this->m_i << endl;
			cout << m_i << endl;
		}
	};
	int main(void){
		A a(...);
		a.print(); // a.print(&a);
		return 0;
	}
  • --> 对于普通成员函数,this指向调用对象的地址

    --> 对于构造函数,this指向正在创建的对象地址

  • 2)多数情况,使用this指针显示访问类中成员和直接访问,没有区别.

    但是以下场景必须使用this指针:

    --> 区分作用域

       通过this可以区分成员变量和形参变量,通过this所访问的一定是成员变量.

    --> 从成员函数返回调用对象自身 //重点掌握

--> 从类的内部销毁对象自身 delete this; // 了解

--> 作为成员函数的实参,实现对象之间的交互 //了解

 

2 常成员函数(常函数)
  • 1) 在成员函数(类中普通的成员函数)参数表的后面加上const修饰,即为常成员函数.

     返回类型 函数名(参数表) const {函数体}

    2) 在常成员函数中this指针是一个常指针,不能在常成员函数中直接修改成员变量的值.

     注: mutable关键字 mutable可变的;易变的

      被mutable关键字修饰的成员变量,可以在常函数中直接修改.

    3) 非const对象既可以调用常函数,也可以调用非常函数(普通函数); //重点掌握

      但是常对象只能调用常函数,不能调用非常函数(普通函数).

      注:常对象也包括常指针和常引用.

      常函数里面不能调用非常函数!

    4) 同一个类中,函数名和参数表相同的成员函数,其常版本和非常版本可以构成重载关系,常对象匹配常版本,非常对象匹配非常版本.

 

十五 析构函数(Destructor)

 

1 语法
	class 类名{
		~类名(void){
			//主要负责清理对象生命周期中的动态资源
		}
	};
	1)函数名必须是"~类名"
	2)没有返回类型,也没有参数
	3)析构函数不能被重载,一个类只能有一个析构函数
2 调用时机
  • 当对象被销毁时,该类的析构函数将自动被执行

     1)栈对象离开所在作用域时,其析构函数被作用域终止的右花括号"}"自动调用.

     2)堆对象的析构函数被delete操作符自动调用.

    注: delete对象时,首先会调用析构函数,清理对象所维护的动态资源,再销毁对象自身;而如果是free仅能销毁对象自身的内存,不会调用析构函数.

3 缺省析构函数
  • 如果类中没有显示的定义析构函数,那么编译器会为该类提供一个缺省的析构函数:

     --> 对于基本类型的成员变量什么也不做

     --> 对于类 类型的成员变量(成员子对象),会自动调用相应类的析构函数.

4 对象的创建和销毁过程
  • 1) 创建

     --> 分配内存

     --> 构建成员子对象(按声明顺序)

     --> 执行构造函数代码

2) 销毁

 --> 执行析构函数代码

 --> 析构成员子对象(按声明逆序)

 --> 释放内存

 

十六 拷贝构造和拷贝赋值

 

1 浅拷贝和深拷贝(面试题)
  • 1) 如果类中包含了指针形式的成员变量,缺省的拷贝构造函数只是复制了指针变量本身,而不是复制了指针所指向的数据,这种拷贝方式被称为浅拷贝.

    2) 浅拷贝将会导致 不同对象的数据共享,同时可能会在析构函数引发"double free"的异常,因此就必须自定义一个支持复制指针所指向数据的拷贝构造函数,即深拷贝.

    浅拷贝:对于指针只是拷贝地址编号,数据共享,double free,内存泄漏(拷贝赋值)

    深拷贝:拷贝指针所指向的内容,而不是拷贝地址编号

2 拷贝赋值
  • 1) 当两个对象进行赋值操作时,比如"i2 = i3",编译器会自动将其翻译成i2.operator=(i3)成员函数调用形式,

    其中"operator="被称为拷贝赋值操作符函数,由该函数实现两个对象的赋值运算,其返回结果就是表达式结果.

    2) 如果自己没有定义拷贝赋值函数,那么编译器会为该类提供一个缺省的拷贝赋值函数,

    但是缺省的拷贝赋值和拷贝构造类似,也是浅拷贝,有"double free",数据共享,内存泄露等问题,为了避免这些问题,需要自定义深拷贝赋值函数:

	类名 &operator = (const 类名 &that){
		if(&that != this){//防止自赋值
			//释放旧内存
			//分配新内存
			//拷贝新数据
		}
		return *this;
	};
  • 注:this 指向左操作数,that 对应右操作数.

 

十七 静态成员(static)

1 静态成员变量
class 类名{
	static 数据类型 变量名; //声明
};
  • 数据类型 类名::变量名 = 初值;//定义和初始化

    1) 普通成员变量属于对象,而静态成员变量不属于对象.

    2) 普通成员变量需要在对象构造时定义和初始化,而静态成员变量需要在类的外部单独定义和初始化.

    3) 静态成员变量和全局变量类似,被存放在数据段,可以把静态成员变量理解为被限制在类中使用的共享资源.

    4) 使用

     类名::静态成员变量;//推荐

     对象.静态成员变量;//本质和上面等价

    注:如果有const修饰的静态成员变量,可以在声明时初始化(特殊,了解)

 

2 静态成员函数
class 类名{
	static 返回类型 函数名(参数表){...}
};
  • 1) 静态成员函数没有this指针,也没有const属性.

    2) 可以把静态成员函数理解为被限制在类中使用的全局函数.

    3) 使用:

     类名::静态成员函数(实参表); //推荐

     对象.静态成员函数(实参表);//本质和上面等价

    注: 在静态成员函数中只能访问静态成员,而非静态成员函数既可以访问静态成员也可以访问非静态的成员.

3 单例模式 书籍<设计模式> (重点)
  • 1) 概念: 一个类只允许存在唯一的对象,并提供它的访问方试.

    2) 实现思路:

     --> 禁止在类的外部创建对象:私有化构造函数(缺省无参构造,拷贝构造).

     --> 类的内部维护唯一的对象:静态成员变量

     --> 提供单例对象的访问方法:静态成员函数

3) 创建方试:

 --> 饿汉式: 单例对象无论用或不用,程序启动即创建.(以空间换时间)

 使用场景:单例对象使用频率高,耗内存小

 特点:以空间换时间

 --> 懒汉式:单例对象用时再创建,不用即销毁.(以时间换空间)

 使用场景:单例对象使用频率低,耗内存多

 特点:以时间换空间

 

十八 成员指针 //了解

1 成员变量指针
  • 1)定义

      类型 类名::*成员指针变量名 = &类名::成员变量;

    2)使用

     对象.*成员指针变量名;

     对象指针->*成员指针变量名;

 注:".*"直接成员指针解引用操作符

 "->*"间接成员指针解引用操作符

 注:指针保存的是成员变量在对象中的相对地址.

 

2 成员函数指针
  • 1)定义

     返回类型 (类名::*成员函数指针)(参数表) = &类名::成员函数名;

    2)使用

     (对象.*成员函数指针)(实参表);

     (对象指针->*成员函数指针)(实参表);

 

十九 操作符重载

基本概念
  • 所谓的操作符重载就是一些具有特殊名字的函数,

    "operator 连接需要重载某个操作符",比如"operator =",

    "operator +"...,通过这样特殊的函数,把已定义的操作符重新定义,完成程序员想要的运算功能.

eg:复数(x + yi)

(1+2i)+(3+4i)=4+6i;

1 双目操作符重载 L#R
1.1 计算类双目操作符:+ - …
  • --> 左右操作数既可以是左值,也可以是右值;

    --> 表达式结果是右值,禁止对表达式结果进行赋值.

    --> 两种实现方式

    1)成员函数形式(左调右参)

     形如L#R表达式将会被编译器自动处理为L.operator#(R)的结果成员函数调用形式,由该函数完成运算功能,函数的返回结果就是表达式结果

     L#R ==> L.operator#(R) const 类名 operator#(const 类名& 形参名)const{}

     修饰返回类型,禁止表达式结果再赋值

     常引用参数,支持常量型右操作数

     常成员函数,支持常量型左操作数

2)全局函数形式(左右操作数都为参数)

 形如L#R表达式将会被编译器自动处理为L.operator#(R)的结果成员函数调用形式,由该函数完成运算功能,函数的返回结果就是表达式结果

 L#R ==> operator#(L,R) const 类名 operator# (const 类名 &l,const 类名 &r){}

注: 通过friend关键字,可以把一个全局函数声明为某个类的友元,友元函数可以访问类中的任何成员.

1.2 赋值类双目操作符:+= -= …
  • --> 左操作数必须是左值,右操作数可以左值也可以是右值

    --> 表示式结果是左值,就是左操作数自身

    --> 两种实现方式

     1) 成员函数形式: L#R ==> L.operator#(R)

     2) 全局函数形式: L#R ==> operator#(L,R)

2 单目操作符的重载 #O
2.1 计算类的单目操作符: ~ -(取负) ! …
  • --> 操作数可以是左值,也可以是右值

    --> 表达式结果是右值,禁止对表达式结果再赋值

    --> 两种实现方式:

     1)成员函数形式: #O ==> O.operator#();

     2)全局函数形式: #O ==> operator#(O);

2.2 自增减的单目操作符: ++,–
  • 1)前++,--

     --> 操作数一定是左值

     --> 表达式结果也是左值,就是操作数自身

      成员函数形式: #O ==> O.operator#();

      全局函数形式: #O ==> operator#(O);

2)后++,--

 --> 操作数一定是左值

 --> 表达式结果是右值,是操作数自增减前副本.

  成员函数形式: #O ==> O.operator#(int/*哑元*/);

  全局函数形式: #O ==> operator#(O,int/*哑元*/);

3 输出(插入)和输入(提取)操作符重载:<< >>
  • 功能: 为了实现自定义类型对象的直接输入或输出功能

    注: 只能使用全局函数形式(friend)

	#include <iostream>
	ostream //标准库定义好的,表示标准输出流类
	istream //标准库定义好的,表示标准输入流类

	//全局函数: operator<<(cout,a)
	ostream &operator<<(ostream &os,const RIGHT &r){
		//....
		return os;
	}

	cout << a << b << c <<...;

	//全局函数: operator>>(cin,a)
	istream &operator>>(istream &is,RIGHT &r){
		//....
		return is;
	}
4 下表操作符:[]
  • 功能:实现自定义类型的对象像数组一样使用

    注: 常对象返回右值,非常对象返回左值.

	string s = "hello";
	s[0] = 'H';arr.operator[](0)
	const string &cs = s;
	cs[0] = 'H'; //error
5 函数操作符: ()
  • 功能:实现自定义类型的对象像函数一样使用(仿函数).

    注: 对参数个数,参数类型,返回左值类型没有任何限制.

	class A{};
	A a;
	a(100,1.23);//a.operator()(100,1.23)
6 new/delete 操作符
	static void* operator new(size_t size){
		...
	}
	static void operator delete(void* pv){
		...
	}
7 操作符的重载限制
  • 1)不是所有符号都能重载,下列操作符不允许重载

      --> 作用域限定操作符"::"

      --> 直接成员访问操作符"."

      --> 直接成员指针解引用操作符".*"

      --> 条件操作符"?:"

      --> 字节长度操作符 "sizeof"

      --> 类型信息操作符 "typeid" //后面讲

2) 如果重载的操作符,所有操作数都是基本类型,则无法被重载.

3) 操作符重载不会改变预定义的优先级和结合性.

4) 操作符重载无法改变操作个数(函数操作符可以).

5) 无法通过操作符重载机制发明新的符号.

6) 只能使用成员函数形式重载的操作符:

  = () [] ->(后面讲)

 

二十 继承(Inheritance)

 

1 继承的概念//了解
  • 通过一种机制表达类型之间共性和特性的方式,利用已有数据类型定义新的数据类型,这种机制就是继承.

    eg:

     人类:姓名,年龄,吃饭,睡觉

     学生类:姓名,年龄,吃饭,睡觉,学号,学习

     教师类:姓名,年龄,吃饭,睡觉,工资,讲课

     ...

     --------------------------------------

     人类:姓名,年龄,吃饭,睡觉

     学生类继承人类:学号,学习

     教师类继承人类:工资,讲课

     ...

 人类(基类/父类)

  /   \

 学生 教师(子类/派生类)

 基类--派生---> 子类

 子类--继承---> 基类

2 继承语法
	class 子类:继承方式 基类1,继承方式 基类2,...{
		
	};
	继承方式:
	1)public:公有继承
	2)protected:保护继承
	3)private:私有继承
3 公有继承的语法特性
  • 1) 子类对象会继承基类的属性和行为,通过子类对象可以访问基类中的成员,好像是基类对象在访问它们.

     注: 子类对象中包含的基类部分可以称为"基类子对象".

    2) 向上造型(upcast) // 重点掌握

     将子类类型的指针或引用转换为基类类型的指针或引用,

     这种操作性缩小的类型转换,在编译器看来是安全的,可以直接隐式转换;

	class A{};//基类
	class B:public A{};//子类
	class C:public A{};//子类
	class D:public A{};//子类
	void func(A *pa){}
	int main(void){
		A a;func(&a);
		B b;func(&b);
		C c;func(&c);
	}
  • 3) 向下造型(downset)

     将基类类型的指针或引用转换为子类类型的指针或引用,

     这种操作性放大的类型转换,在编译器看来是危险的,不能隐式转换,可以显式转换(推荐静态转换static_cast)

4) 子类继承基类的成员

 --> 在子类中,可以直接访问基类中的公有或保护成员,就好像它们声明在子类中一样.

 --> 对于基类中的私有成员,子类也可以继承,但是会受到访问控制属性的限制,无法直接访问.但是可以让基类提供公有或保护提供的接口函数,来间接访问.

5)子类隐藏基类的成员

 -->如果子类和基类中定义了同名的成员函数,因为作用于不同不能构成重载关系,而是隐藏关系.

 -->如果需要通过子类访问基类中被隐藏的成员,可以借助"类名::"显示声明 //推荐

 -->也可以通过using声明将基类中的成员函数引入到子类作用域中,让它们在子类中形成重载,通过参数重载解析解决.

4 访问控制属性和继承方式
  • 1) 访问控制属性:影响访问该类成员的位置

访问控制访问控制内部子类外部友元
限定符属性访问访问访问访问
public公有成员okokokok
protected保护成员okoknook
private私有成员oknonook

2)继承方式:影响通过子类访问方式基类中成员的可访问性

基类中成员公有继承子类保护继承子类私有继承子类
公有成员公有成员保护成员私有成员
保护成员保护成员保护成员私有成员
私有成员私有成员私有成员私有成员

注:向上造型语法特性在保护继承和私有继承中不在使用.

5 子类的构造函数
  • 1) 如果子类的构造函数没有指明基类子对象的初始化方式,将会自动选择基类的无参构造函数来初始化.

    2) 如果希望基类子对象以有参的方式被初始化,则必须要使用初始化列表显示表示.

    3)子类对象创建过程

     --> 分配内存

     --> 构造基类子对象(按继承表从左到右顺序)

     --> 构造成员子对象(按声明自上而下顺序)

     --> 执行子类构造函数代码

6 子类的析构函数
  • 1) 子类的析构函数,无论是自定义还是缺省的,都会自动调用基类的析构函数,完成对基类子对象的销毁操作.

    2) 关于子类的销毁过程

     --> 执行子类析构函数代码

     --> 析构成员子对象(按声明逆序)

     --> 析构基类子对象(按继承表逆序)

     --> 释放内存

    3) 基类的析构函数不会自动调用子类析构函数,所以如果delete一个指向子类对像的基类指针,实际被执行的仅是基类的析构函数,

    子类的析构函数执行不到,有内存泄露的风险!

	class A{};
	class B:public A{};
	A *pa = new B;//pa:指向子类对象的基类指针
	delete pa;//有内存泄露的风险
  • 解决方法:虚析构函数(后面讲)

     delete (B*)pa;//也可以解决问题,但容易遗漏,不推荐

7 子类的拷贝构造和拷贝赋值
  • 1)子类的拷贝构造

     --> 如果子类没有定义拷贝构造函数,那么编译器会为子类提供缺省的拷贝构造函数,它自动调用基类的拷贝构造函数,完成基类子对象的拷贝操作

     --> 如果自定义了拷贝构造函数,编译器不在为子类提供缺省的拷贝构造,这是需要使用初始化列表,显示指明基类子对象也要以拷贝的方式进行初始化.

	class Base{};
	clase Derived:public Base{
		//Base(that):指明基类子对象以拷贝方式初始化
		Derived(const Derived &that):Base(that){}
	};
  • 2) 子类的拷贝赋值

     --> 如果子类没有自己定义拷贝赋值函数,那么编译器会为子类提供一个缺省的拷贝构造赋值函数,该函数会自动调用基类的拷贝赋值函数,完成基类子对象的拷贝操作.

     --> 如果子类自己定义了拷贝赋值函数,那么编译器不在为子类提供缺省的拷贝赋值函数,这时需要显示的调用基类的拷贝赋值函数,完成基类子对象的拷贝操作.

	class Base{};
	class Derived:public Base{
		Derived &operator=(const Derived &that){
		if(&that != this){
			//显示调用基类的拷贝赋值函数,完成基类子对象的赋值操作
			Base::operator=(that);
			}
		}
	};
  • 对象自恰性
8 多重继承
  • 1) 概念

     一个子类同时继承多个基类,这样的继承方式被称为多重继承

    2) 向上造型时,编译器会根据各个基类子对象的内存布局,自动进行偏移计算,保证指针的类型和所指向的目标子对象类型一致.

    3) 名字冲突问题

     --> 多重继承,如果多个基类中存在相关的名字,通过子类访问时,编译器将会报歧义错误--名字冲突.

     --> 解决名字冲突的通过做法是显示的使用"类名::",指明所访问的名字属于那个基类. // 推荐

     --> 如果相同的名字是成员函数,并满足不同参重载条件,也可以使用using声明,让他们在子类中形成重载,通过参数的重载解析来解决.

9 钻石继承和虚继承
  • 1) 一个子类的多个基类同时继承共同的基类祖先,这样继承结果称为钻石继承

      A (int m_data)

     /  \

    B   C //:virtual public A

     \  /

      D //负责构造公共基类子对象

     注: A称为公共基类,B,C称为中间类,D称为末端子类

2) 创建末段子类D对象时,会包含多个公共基类(A)子对象,通过末端子类访问公共基类的成员,会因为继承路径不同而导致结果不一致.

 解决方法 虚继承 虚基类表

3) 通过虚继承可以让公共基类子对象(A)在末端子类对象(D)中实现唯一,并且可以为所有的中间类(B,C)共享,这样即使沿着不同的继承路径,所访问到公共基类的成员也是一致的.

4) 虚继承语法:

 --> 在继承表使用virtual关键字修饰

 --> 位于继承链的末端子类负责构造公共基类子对象.

----------------------------------------------------------------------------------

 eg:实现图形库,可以绘制各种图形

 图形基类(位置/绘制)

 矩形(宽和高/绘制)  圆形(半径/绘制)

 

二十一 多态(Polymorphic)

 

1 虚函数覆盖(函数重写),多态的概念
  • 1) 如果将基类中某个成员声明为虚函数,那么子类中具有与其相同原型的成员函数就也变成虚函数,并且对基类子对象中的版本形成覆盖,即函数重写.

    2) 满足了虚函数覆盖,再通过指向子类对象的基类指针或引用子类对象的基类引用,再调用虚函数,实际被执行的将是子类中的覆盖版本,

    而不再是基类中原始版本,这种语法现象被称为多态.

	class A{ virtual void func(){} };
	class B:public A{ void func(){}};
	B b;
	A* pa = &b;//pa:指向子类对象的基类指针
	A& ra = b;//ra:引用子类对象的基类引用
	pa->func();//实际被执行的将是子类中的覆盖版本
	ra.func();//实际被执行的将是子类中的覆盖版本
2 虚函数覆盖条件
  • 1)只有类中成员函数才能声明为虚函数,而静态成员函数,构造函数和全局函数都不能为虚函数.

     析构函数可以为虚函数(后面讲)

    2)虚函数的virtual关键字修饰必须写在基类中,而与子类中的virtual无关.

    3)虚函数在子类中的版本和基类中的版本必须具有相同的函数签名,即函数名,参数表,常属性一致.

    4)如果虚函数在基类中版本返回基本类型的数据,那么子类型中覆盖版本必须返回相同类型的数据

    5)如果虚函数在基类中版本返回其类类型的指针(A*)或引用(A&),那么允许子类中覆盖版本返回其子类型的指针(B*)或引用(B&)

	class A{};
	class B:public A{};
3 多态的条件
  • 1)多态的语法特性除了要满足虚函数覆盖特性,还必须通过指针或引用调用虚函数,才能满足条件.

    2)调用虚函数的指针也可以是this指针,当通过子类对象调用基类中的成员函数时,该成员函数中的this指针将是一个指向子类对象的基类指针,

    再通过它调用虚函数,同样可以表现多态的语法特性//重点掌握

eg: Qt多线程
	class QThread{//Qt官方写好表示线程类
	public:
		void start(void){
			run();
		}
	protected:
		//线程入口函数
		virtual void run(void){
			...
		}
	};
	class MyThread:public QThread{
	protected:
		//覆盖基类线程入口函数
		void run(void){
			想在子线程中执行的代码...
		}
	};
	MyThread thread;
	thread.start();
4 多态的实现原理 //了解
  • 通过虚函数表和动态绑定实现的.

    虚表指针 函数指针数组 函数指针  虚函数地址

         *虚表指针  **虚表指针  ***虚表指针

    1)虚函数表的生成会增加内存开销,在编译时生成

    2)动态绑定的过程会增加时间效率,在运行时进行

    3)虚函数不能被内联优化

     注:如果没有多态的语法要求,最好不要使用虚函数

5 纯虚函数,抽象类和纯抽象类(面试笔试)
  • 1) 纯虚函数

     virtual 返回类型 函数名(形参表) = 0;

    2) 抽象类

     如果类中成员函数有纯虚函数,那么该类就是抽象类.

     抽象--具体.

     注:抽象类不能创建对象

    3) 纯抽象类

     如果类中所有的成员函数都是纯虚函数,那么该类就是纯抽象类.

     工厂制造模式(好莱坞模式)

      职责分离

6 虚析构函数
  • 1)基类的析构函数不会自动调用子类析构函数,

     所以如果delete一个指向子类对象的基类指针,

     实际被执行的仅是基类的析构函数,子类的析构函数执行不到,有内存泄露的风险!

     //解决:虚析构函数

    2)如果将基类的析构函数声明为虚函数(虚析构函数),这时子类的析构函数就也是虚函数,

     并且可以对基类的虚析构函数形成覆盖,也可以体现多态的语法特性;

     这是delete一个指向子类对象的基类指针,实际被执行的将是子类的析构函数,

     而子类的析构函数在执行结束后将会自动调用基类的析构函数,避免内存泄露.

 

二十二 运行时类型信息 //了解

1 typeid操作符
  • #include <typeinfo>

    typeid(类型/对象),返回typeinfo对象用于描述类型信息

    sizeof(类型/对象),size_t

    注:typeinfo类中包含name成员函数,可以获得字符串形式的类型信息

    注:typeinfo类中支持"== !="操作符,通过它们可以直接进行类型之间的比较,如果类型之间具有多态的继承关系,

    typeid还可以利用多态语法确定实际的目标对象类型.

2 dynamic_cast操作符
  • 语法:

     目标变量 = dynamic_cast<目标类型>(源类型变量)

    适用场景:

     主要用于具有多态继承关系,指针或引用的显示转换.

     注:在转换过程中,会检查目标变量类型和期望转换的类型是否一致.

     如果一致则转换成功,如果不一致则失败,

     如果转换指针返回NULL表示失败,如果转换是引用,抛出异常"bad_cast"表示失败.

 

二十三 C++异常机制

 

1 软件开发常见错误
  • 1) 语法错误

    2) 逻辑错误

     gdb调试工具

    3) 功能错误

    4) 设计的缺陷

    5) 需求不符

    6) 环境异常

    7) 操作不当

2 传统C的错误处理机制
  • 1)通过返回值返回值错误

     优点:函数调用路径中所有的栈对象都可以得到正确的析构函数,不会内存泄露

     缺点:错误处理流程比较麻烦,需要对函数调用路径逐层进行返回值判断,代码臃肿

2)通过远程跳转处理错误

 优点:不需要进行逐层返回值判断,实现一步到位错误处理,代码精炼.

 缺点:函数调用路径中的栈对象失去被析构机会,有内存泄露的机会.

3 C++异常机制
  • 1) 异常抛出

     throw 异常对象;

     注:异常对象可以基本类型,也可以是类类型

    2) 异常检测和捕获

	try{
		//可能引发异常语句
	}
	catch(异常类型1){
		针对异常类型1的处理
	}
	catch(异常类型2){
		针对异常类型2的处理
	}
	...
  • 注:catch子句根据异常对象类型自上而下顺序匹配,而非最优匹配.因此对子类类型异常捕获语句要写在前面,

    不能写在对基类类型异常捕获语句后面,否则将被基类类型的异常捕获语句提前截获.

4 函数的异常说明
  • 1) 用于说明函数可能抛出的异常类型

     返回类型 函数名(形参表) throw(异常类型表) {函数体}

    2) 函数的异常声明是一种承诺,表示该函数所抛出的异常不会超出说明的范围,

     如果函数的实现者抛出了异常说明以外的类型,是无法被调用者正常捕获,而会被系统捕获,导致进程终止.

    3) 两种极端形式

     --> 不写异常说明,表示可以抛出任何异常

     --> 空异常说明,"throw"表示不会抛出任何异常

    4) 如果函数的声明和定义分开写时,要保证异常说明类型一致,但是顺序无所谓.

     //补充虚函数覆盖条件:

    5)如果基类中的虚函数带有异常说明,那么该函数在子类的覆盖版本不能说明比基类版本抛出更多的异常,否则将会因为"放松throw限定"导致编译失败.

5 标准异常类: exception
	class exception
	{
	public:
	exception()  throw/*_GLIBCXX_USE_NOEXCEPT*/ { }
	virtual ~exception() throw()/*_GLIBCXX_USE_NOEXCEPT*/;
		/** Returns a C-style character string describing the general cause
		*  of the current error.*/
	virtual const char* what() const throw() /*_GLIBCXX_USE_NOEXCEPT*/;
	};
	try{}
	catch(exception &ex){
		ew.what();
	}
6 关于构造和析构函数的异常//了解
  • 1) 构造函数可以抛出异常,但是对象将会被不完整创建,这样的对象的析构函数不再会自动被执行,

     因此在构造函数抛出异常之前,需要手动释放之前分配的动态资源.

    2) 析构函数最好不要抛出异常.

 

二十四 I/O流 //了解

 

1 主要的I/O流类
  •              ios

            /            \

          istream          ostream

         /    |    \   /    |     \

     istrstream    ifstream   iostream    ofstream   ostrstream

scanf()/sscanf()/fscanf()

printf()/sprintf()/fprintf()

2 格式化I/O
  • 1) 格式化函数(成员函数)
	cout << 20/3.0 << endl;//6.66666 六个有效数字
	cout.precision(10); 改变输出精度
	cout << 20/3.0 << endl; // 6.66666666
  • 2) 流控制符(全局函数)
	cout << 20/3.0 << endl;//6.66666 六个有效数字
	cout << setprecision(10) << 20/3.0 << endl;//6.666666666
	endl(流控制符)
3 字符串I/O
	#include <strstream> //过时,不要使用
	istrstream/ostrstream

	#inlcude <sstream>//推荐
	istringstream //类似sscanf() "123"->123
	ostringstream //类似sprintf() 123->"123"
4 文件I/O
	#include <fstream>
	ifstream //类似fscanf();
	ofstream //类似fprintf();
5 二进制I/O
	//类似fread()
	istream &istream::read(char *buf,streamsize num);
	//类似fwrite()
	ostream &ostream::write(const char *buf,size_t num);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值