目录
1 C++简介
1.1 起源
1.2 应用范围
1.3 C++和C
2开发工具
3 基本语法
3.1 注释
3.2关键字
3.3标识符
4 数据类型
4.1基本数据类型
4.2 数据类型在不同系统中所占空间大小
4.3 typedef声明
4.4 枚举类型
5 变量
5.1 变量的声明和定义
5.2 变量的作用域
6 运算符
7 语法结构
7.1 循环结构
7.2 判断结构
7.3 三元运算符
7.4 预处理命令
8 数组
8.1一维数组
8.2二维数组
8.3 指向数组的指针
8.4 数组与new(动态创建数组)
8.5 数组与函数
数组->函数
函数返回数组
8.6 获取数组的大小
9 函数
9.1 函数声明与定义
9.2 函数的参数与返回值
9.3 函数调用
9.4 函数重载
9.5 内联(inline)函数
9.6 洞悉内联函数底层原理
10 字符串(string)
10.1 C风格的字符串(字符数组)
10.2 C++中的字符串(string)
11 指针和引用
11.1 指针
11.2 引用
12 自定义数据类型
12.1 结构体
12.2 结构体大小和字节对齐
12.3 公用体(union)
12.4 枚举(enum)和typedef声明
13 面向对象
13.1 类
13.2 类成员的访问权限以及类的封装
13.3 对象
13.4 构造函数
13.5 析构函数
13.6 对象指针
13.7 静态成员
13.8 友元
13.9 类(class)与结构体(struct)的区别
14 继承和派生
14.1 继承和派生概述
14.2 多继承
14.3 虚基类
15 多态和虚函数
15.1 向上转型
15.2 多态
15.3 虚函数
15.4 纯虚函数
16 运算符重载
16.1 定义
16.2 形式
16.3 常用运算符的重载
16.4 实现类型转换
17 IO流
17.1 流类和对象
17.2 标准输入输出流
18 文件操作
18.1 文件类和对象
18.2 打开文件
18.3 文本文件的读写
18.4 二进制文件的读写
18.5 移动和获取文件读写指针
18.6 文本文件和二进制文件打开方式的区别
19 泛型和模板
19.1 函数模板
19.2 类模板
19.3 typename 和 class 的区别
19.4 强弱类型语言和c++模板的那点猫腻
20 命名空间和异常处理
20.1 命名空间
20.2 异常处理
21 STL
21.1 容器
21.2 迭代器
21.3 算法
1 C++简介
1.1 起源
-贝尔实验室20世纪80年代(1979)
1.2 应用范围
- 文字处理程序以及电子表格
- 编译器
- 操作系统
- 大型游戏等
1.3 C++和C
- C语言是结构化和模块化的语言,面向过程。
- C++保留了C语言原有的所有优点,增加了面向对象的机制,俗称“带类的C”,1983年更名为C++
2开发工具
- 记事本(Notepad++)+命令行
- Visual C++ 6.0:经典开发工具,与流行操作系统有冲突
- VS 2015等:功能强大,体积也强大
- Code::Blocks:开源免费开发工具,专业开发人员推荐使用
- 其他开发工具:DeV C++、CLion、C-Free、Xcode、C4droid
3 基本语法
- 对象-对象具有状态的行为。对象是类的实例。
- 类-类可以定义为对象行为、状态的模版。
- 方法-从基本上讲,一个方法表示一种行为,一个类可以包含多种方法。
- 变量
3.1 注释
//单行注释/*多行注释多行注释*/
3.2关键字
asm | else | new | this |
---|---|---|---|
auto | enum | operator | throw |
bool | explicit | private | true |
break | export | protected | try |
case | extern | public | typedef |
catch | false | register | typeid |
char | float | reinterpret_cast | typename |
class | for | return | union |
const | friend | short | unsigned |
const_cast | goto | signed | using |
continue | if | sizeof | virtual |
default | inline | static | void |
delete | int | static_cast | volatile |
do | long | struct | wchar_t |
double | mutable | switch | while |
dynamic_cast | namespace | template |
3.3标识符
- 标识符是用来标识变量、函数、类、模块,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。
- 标识符内不允许出现标点字符,比如 @、& 和 %。C++ 是区分大小写的编程语言。
4 数据类型
4.1基本数据类型
七种基本的C++数据类型:bool、char、int、float、double、void、wchar_t
类型修饰符:signed、unsigned、short、long
注:一些基本类型可以使用一个或多个类型修饰符进行修饰,比如:signed short int简写为short、signed long int 简写为long。
类型名 | 占用字节数 | 数值范围 |
---|---|---|
void | 0 | |
bool | 1 | {true.false} |
wchar_t | 2或4个字节 | |
char(signed char) | 1 | -128~+127 |
short(signed short) | 2 | -32768~+32767 |
int(signed int) | 4 | -2147483648~+2147483647 |
long(signed long) | 4 | -2147483648~+2147483647 |
long long(signed long long) | 8 | -9,223,372,036,854,775,808 ~9,223,372,036,854,775,807 |
float | 4 | -.34*1038~3.4*1038 |
double | 8 | -1.7*10308~1.7*10308 |
unsigned char | 1 | 0~255 |
unsigned shrot | 2 | 0~65525 |
unsigned(unsigned int) | 4 | 0~4294967295 |
unsigned long | 4 | 0~4294967295 |
unsigned long long | 8 | 0 ~ 18,446,744,073,709,551,615 |
//x64处理器 64位window10 vs2015 #include using namespace std;int main(){bool b;char c;short s; int i; long l; long long ll; float f; double d; long double ld;long float lf;unsigned char uc; unsigned short us; unsigned int ui; unsigned long ul; unsigned long long ull;cout << sizeof(bool) << endl;cout << sizeof(char)<<" " << sizeof(short)<<" "<< sizeof(signed int) << " " << sizeof(long) << " " << sizeof(signed long long) << " " << sizeof(float) << " " << sizeof(double) << " " << sizeof(long float) << " " << sizeof(long double) << endl;cout <<sizeof(unsigned char)<<" "<< sizeof(unsigned short) << " " << sizeof(unsigned int) << " " << sizeof(unsigned long) << " " << sizeof(unsigned long long) << endl;cout << sizeof(unsigned) << endl;cout << "hello World!!!" <<endl;system("pause");return 0;}
4.2 数据类型在不同系统中所占空间大小
这个与机器、操作系统、编译器有关。比如同样是在32bits的操作系统系,VC++的编译器下int类型为占4个字节;而tuborC下则是2个字节。
原因:
- c/c++规定int字长和机器字长相同
- 操作系统字长和机器字长未必一致
- 编译器根据操作系统字长来定义int字长
类型 | 16位操作系统 | 32位操作系统 | 64位操作系统 |
---|---|---|---|
char | 1 | 1 | 1 |
char* | 2 | 4 | 8 |
short | 2 | 2 | 2 |
int | 2 | 4 | 4 |
long | 4 | 4 | 8 |
long long | 8 | 8 | 8 |
注:long类型在不同编译器中的占位不一样: 32位时,VC++和GCC都是4字节; 64位时,VC++是4字节,GCC是8字节。
4.3 typedef声明
//使用typedef为一个已有的类型取一个新的名字,语法如下:typedef type newname//eg:typedef int feetfeet distance
4.4 枚举类型
C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合;枚举元素是一个整型,枚举型可以隐式的转换为int型,int型不能隐式的转换为枚举型。
//枚举类型的语法:enum 枚举名{标识符[=整型常数], 标识符[=整型常数], ... 标识符[=整型常数]}枚举变量;
如果枚举没有初始化, 即省掉”=整型常数”时, 则从第一个标识符开始;
- 默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。
例如:
enum course {math,chinese,english,physics,chemistry}c;c = english;cout<<c<<endl; //2//english为1 physics为2 chemistry为3,chinese仍为1,math仍为0enum course {math,chinese,english=1,physics,chemistry};
5 变量
变量其实只不过是程序可操作的存储区的名称。C++ 中每个变量都有指定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。
5.1 变量的声明和定义
- 变量声明向编译器保证变量以给定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。
- 可以在 C++ 程序中多次声明一个变量,但变量只能在某个文件、函数或代码块中被定义一次。
- 多个变量赋同一个值时,需要分别赋值。
int x = y = z = 66;//错误int x = 3,y = 3,z = 3;int x, y ,z = 3;x = y = z;
变量的声明(不分配内存):extern 数据类型 变量名;
变量的定义:数据类型 变量名1,变量名2,...变量名n;
// 变量声明extern int a, b;int main (){ // 变量定义 int a, b; // 初始化 a = 23; b = 25; return 0;}
5.2 变量的作用域
局部变量:在函数或一个代码块内部声明的变量,称为局部变量。它们只能被函数内部或者代码块内部的语句使用。
全局变量:在所有函数外部定义的变量(通常是在程序的头部),称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。
- 局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值。
- 当局部变量被定义时,系统不会对其初始化;定义全局变量时,系统会自动初始化值:
int float double 0,char ’\0‘,指针 NULL
int i = 66;int main (){ int i = 88; cout << i<<endl;//8 return 0;}float f;double d;char c;int *p;int main(){cout << i << f << d << c << p << endl;//000 00000000return 0}
6 运算符
- 算术运算符:
+ - * / % ++ --
- 关系运算符:
== != >= <=
- 逻辑运算符:
&& || !
- 位运算符:
& | ^ ~ <>
- 赋值运算符:
= += -= *= /= %= <>= &= ^= !=
- 杂项运算符:
sizeof //返回变量的大小,eg:sizeof(a)返回4 a是整型 sizeof(int)Condition" />
- for each
STL中的for增强循环。
int a[4] = { 4,3,2,1 };for each (int var in a){cout << var << " ";}
7.2 判断结构
- if
if(expr){statement;//如果expr为true将执行的语句块 }if(expr){ statement1;// 如果expr为true将执行的语句块}else{ statement2;// 如果expr为false将执行的语句}if(expr1){ statement1;// 如果expr1为true将执行的语句块}elseif(expr2){ statement2;// 如果expr2为true将执行的语句块}...else{statementElse;// 当上面的表达式都为false执行的语句块}
- switch
switch(expression){ case constant-expression : statement(s); break; case constant-expression : statement(s); break; // 您可以有任意数量的 case 语句 default : // 可选的 statement(s);}
- 每个case后满的常量表达式必须各不相同。
- case语句和default语句出现的顺序对执行结果没有影响。
- 若case后没有break,执行完就不会判断,继续执行下一个case语句。直到遇到brerak。
- default后面如果没有case,则break可以省略
- 多个case可以用一组执行语句
char c = 'A';switch (c){case 'A':case 'B':case 'C':cout << "及格了" << endl;break;default:cout << "不及格" << endl;}
7.3 三元运算符
//如果 Exp1 为真,则计算 Exp2 的值,结果即为整个 ? 表达式的值。如果 Exp1 为假,则计算 Exp3 的值,结果即为整个 ? 表达式的值Exp1 ? Exp2 : Exp3;
7.4 预处理命令
预处理程序(删除程序注释,执行预处理命令等)–>编译器编译源程序
- 宏定义:
#define 标识符 字符串
- 文件包含:
#include 或者#include“filename”
- 条件编译
//如果标识符被#define定义过,执行程序段1,否则执行程序段2#ifdef 标识符程序段1#else程序段2#endif//如果标识符没有被#define定义过,执行程序段1,否则执行程序段2#ifndef 标识符程序段1#else程序段2#endif//如果表达式为true,执行程序段1,否则执行程序段2#if 表达式程序段1#else程序段2#endif
8 数组
一些具有相同数据类型或相同属性(类)的数据的集合,用数据名标识,用下标或序号区分各个数据。数组中的数据称为元素。
8.1一维数组
定义一维数组的形式:数据类型 数据名[常量表达式]
初始化的形式:数据类型 数组名[常量表达式] = {初值表};
为数组的某一个元素赋值:数组名[下标] =值
(下标从0开始)
数组的引用:数组名[下标]
- 初始化数组时,可以只给部分数组元素赋值
- 对全部元素数组赋值时,可以不指定数组长度,编译系统会根据初值个数确定数组的长度。
- static型数组元素不赋初值,系统会自动默认为0。
int arr1[4] = {1,2,3,4};int arr2[4] = { 1,2 };int arr[4] = {0];//所有元素为0static int arr3[3];int arr4[4];cout << "arr1:"<
8.2二维数组
定义一维数组的形式:数据类型 数据名[常量表达式1][常量表达式2]
初始化的形式:数据类型 数组名[常量表达式1] [常量表达式2]= {初值表};
为数组的某一个元素赋值:数组名[行下标][列下标] =值
(下标从0开始)
数组的引用:数组名[行下标][列下标]
- 将所有数据写在一个花括号内,自动按照数组元素个数在内存中排列的顺序赋值
- 可对部分元素赋值,其余元素的值自动取0.
- 定义初始化数组时,可以省略第一维的长度,第二维不能省,系统会自动确认行数
int arr1[2][3];int arr[2][3] = {0];//所有元素为0int arr2[2][3] = { {1,2,3},{4,5,6} };int arr3[2][3] = { 1,2,3 ,4,5,6 };int arr4[2][3] = { {1},{4,6} };int arr5[][3] = { 1,2,3 ,4,5,6 };
有默认值参数的函数
int sum(int a, int b=2){ return (a + b);} int main (){ cout << "Total value is :" << sum(100, 200);<< endl;//Total value is :300 cout << "Total value is :" << sum(100);<< endl;//Total value is :102 return 0;}
函数的返回值
- 返回值通过return给出,return后面跟表达式,且只能放回一个值;如果没有表达式,可以不写return;return后面的括号可有可无。
- return语句中的表达式类型应与函数类型一致,否则自动转换类型(函数类型决定返回值类型)
9.3 函数调用
- 函数可以单独作为一个语句使用。有返回值的函数,可将函数调用作为语句的一部分,利用返回值参与运算。
函数调用形式:参数传递–>函数体执行–>返回主调函数
函数名(实参列表);show();
函数的嵌套调用:
int a(){return 666;}int b(int sum){return sum+a()}int main(){cout<<b(222)<<endl;//888return 0;}
函数的递归调用:直接递归调用和间接递归调用
- 一个函数直接或间接递归调用该函数本身,称为函数的递归调用
- 递归和回归:原问题=>子问题 子问题的解=>原问题的解
//直接递归调用:求1+...n的值int total(int sum){if (sum == 1){return 1;}return sum + total(sum - 1);}int main(){cout << "total = " << total(10) << endl;//total = 55system("pause");return 0;}//间接递归调用int f2();int f1(){... f2()}int f2(){f1();}
9.4 函数重载
同一个函数名对应不同的函数实现,每一类实现对应着一个函数体,名字相同,功能相同,只是参数的类型或参数的个数不同。
多个同名函数只是函数类型(函数返回值类型)不同时,它们不是重载函数
int add(int a,int b){return a+b;}double add(double a,double b){return a+b;}int add(int a,int b,int c){return a+b+c;}
9.5 内联(inline)函数
c++在编译时可以讲调用的函数代码嵌入到主调函数中,这种嵌入到主调函数中的函数称为内联函数,又称为内嵌函数或内置函数。
- 定义内联函数时,在函数定义和函数原型声明时都使用inline,也可以只在其中一处使用,其效果一样。
- 内联函数在编译时用内联函数函数的函数体替换,所以不发生函数调用,不需要保护现场,恢复现场,节省了开销。
- 内联函数增加了目标程序的代码量。因此,一般只将函数规模很小且使用频繁的函数声明为内联函数。
- 当内联函数中实现过于复杂时,编译器会将它作为一个普通函数处理,所以内联函数内不能包含循环语句和switch语句。
内联函数格式如下:
inline 函数类型 函数名(形参列表){函数体;}inline int add(int a, int b){return a + b;}
9.6 洞悉内联函数底层原理
1.使用Visual Studio 2015创建一个C++Win32控制台程序,点击项目->项目属性设置内联函数优化
从以上代码代码可以看出,在主函数中调用(call)了add函数。
5.在内联函数中添加几个循环后,编译器就把内联函数当做普通函数看待了,代码如下:
inline int add(int a, int b){int sum = 0;for (int i = 0; i < 100; i++)a++;for (int i = 0; i < 100; i++){for (int i = 0; i < 100; i++){sum++;}}return a + b;}int main(){int result = add(12, 34);cout << result << endl;return 0;}
int result = add(12, 34);00181A4E push 22h 00181A50 push 0Ch 00181A52 call add (01814ECh) ///00181A57 add esp,8 00181A5A mov dword ptr [result],eax cout << result << endl;00181A5D mov esi,esp 00181A5F push offset std::endl<char,std::char_traits > (01810A5h) 00181A64 mov edi,esp 00181A66 mov eax,dword ptr [result] 00181A69 push eax 00181A6A mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (018D098h)] cout << result << endl;00181A70 call dword ptr [__imp_std::basic_ostream<char,std::char_traits >::operator<< (018D0A8h)] 00181A76 cmp edi,esp 00181A78 call __RTC_CheckEsp (01811C7h) 00181A7D mov ecx,eax 00181A7F call dword ptr [__imp_std::basic_ostream<char,std::char_traits >::operator<< (018D0ACh)] 00181A85 cmp esi,esp 00181A87 call __RTC_CheckEsp (01811C7h) return 0;00181AA3 xor eax,eax
10 字符串(string)
10.1 C风格的字符串(字符数组)
C风格的字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。
- 输入字符串长度一定小于已定义的字符数组长度,最后一位是/0终止符号;不然输出时无法知道在哪里结束。
字符数组的定义和初始化
char a[5]//字符个数不够,补0; 字符个数超过报错char str[7] = {'h','e','i','r','e','n'};char str[] = {'h','e','i','r','e','n'};cin>>str;//输入 输入字符串长度一定小于已定义的字符数组长度cout<<str;//输出
字符串的处理函数
strcat(char s1[],const char s2[]);//将s2接到s1上strcpy(char s1[],const char s2[]);//将s2复制到s1上strcmp(const char s1[],const char s2[]);//比较s1,s2 s1>s2返回1 相等返回1,否则返回-1strlen(char s[]);//计算字符串s的长度 字符串s的实际长度,不包括\0在内
10.2 C++中的字符串(string)
字符串的定义和初始化
//定义string 变量;string str1;//赋值string str2 = "ShangHai";string str3 = str2;str3[3] = '2';//对某个字符赋值//字符串数组string 数组名[常量表达式]string arr[3];
字符串的处理函数
#include #include #include string str;//生成空字符串string s(str);//生成字符串为str的复制品string s(str, strbegin,strlen);//将字符串str中从下标strbegin开始、长度为strlen的部分作为字符串初值string s(cstr, char_len);//以C_string类型cstr的前char_len个字符串作为字符串s的初值string s(num ,c);//生成num个c字符的字符串string s(str, stridx);//将字符串str中从下标stridx开始到字符串结束的位置作为字符串初值size()和length();//返回string对象的字符个数max_size();//返回string对象最多包含的字符数,超出会抛出length_error异常capacity();//重新分配内存之前,string对象能包含的最大字符数>,>=,<,<=,==,!=//支持string与C-string的比较(如 str,>=,<,<=这些操作符的时候是根据“当前字符特性”将字符按字典顺序进行逐一得 比较,string (“aaaa”) <string(aaaaa)。 compare();//支持多参数处理,支持用索引值和长度定位子串来进行比较。返回一个整数来表示比较结果,返回值意义如下:0:相等 1:大于 -1: push_back() insert( size_type index, size_type count, CharT ch );//在index位置插入count个字符chinsert( size_type index, const CharT* s );//index位置插入一个常量字符串insert( size_type index, const CharT* s, size_type n);//index位置插入常量字符串insert( size_type index, const basic_string& str );//index位置插入常量string中的n个字符insert( size_type index, const basic_string& str, size_type index_str, size_type n);//index位置插入常量str的从index_str开始的n个字符insert( size_type index, const basic_string& str,size_type index_str, size_type count = npos);//index位置插入常量str从index_str开始的count个字符,count可以表示的最大值为npos.这个函数不构成重载 npos表示一个常数,表示size_t的最大值,string的find函数如果未找到指定字符,返回的就是一个npositerator insert( iterator pos, CharT ch );iterator insert( const_iterator pos, CharT ch );void insert( iterator pos, size_type n, CharT ch );//迭代器指向的pos位置插入n个字符chiterator insert( const_iterator pos, size_type count, CharT ch );//迭代器指向的pos位置插入count个字符chvoid insert( iterator pos, InputIt first, InputIt last );iterator insert( const_iterator pos, InputIt first, InputIt last );append() 和 + 操作符//访问string每个字符串string s1("yuanrui"); // 调用一次构造函数// 方法一: 下标法for( int i = 0; i < s1.size() ; i++ ) cout<<s1[i];// 方法二:正向迭代器for( string::iterator iter = s1.begin();; iter < s1.end() ; iter++) cout<<*iter; // 方法三:反向迭代器for(string::reverse_iterator riter = s1.rbegin(); ; riter < s1.rend() ; riter++) cout<<*riter; iterator erase(iterator p);//删除字符串中p所指的字符iterator erase(iterator first, iterator last);//删除字符串中迭代器区间[first,last)上所有字符string& erase(size_t pos = 0, size_t len = npos);//删除字符串中从索引位置pos开始的len个字符void clear();//删除字符串中所有字符string& replace(size_t pos, size_t n, const char *s);//将当前字符串从pos索引开始的n个字符,替换成字符串sstring& replace(size_t pos, size_t n, size_t n1, char c); //将当前字符串从pos索引开始的n个字符,替换成n1个字符cstring& replace(iterator i1, iterator i2, const char* s);//将当前字符串[i1,i2)区间中的字符串替换为字符串s//tolower()和toupper()函数 或者 STL中的transform算法string s = "ABCDEFG";for( int i = 0; i < s.size(); i++ ) s[i] = tolower(s[i]);transform(s.begin(),s.end(),s.begin(),::tolower);size_t find (constchar* s, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找子串s,返回找到的位置索引,-1表示查找不到子串size_t find (charc, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找字符c,返回找到的位置索引,-1表示查找不到字符size_t rfind (constchar* s, size_t pos = npos) const;//在当前字符串的pos索引位置开始,反向查找子串s,返回找到的位置索引,-1表示查找不到子串size_t rfind (charc, size_t pos = npos) const;//在当前字符串的pos索引位置开始,反向查找字符c,返回找到的位置索引,-1表示查找不到字符size_tfind_first_of (const char* s, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符size_tfind_first_not_of (const char* s, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找第一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符size_t find_last_of(const char* s, size_t pos = npos) const;//在当前字符串的pos索引位置开始,查找最后一个位于子串s的字符,返回找到的位置索引,-1表示查找不到字符size_tfind_last_not_of (const char* s, size_t pos = npos) const;//在当前字符串的pos索引位置开始,查找最后一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到子串sort(s.begin(),s.end());substr(pos,n);//返回字符串从下标pos开始n个字符strtok()char str[] = "I,am,a,student; hello world!";const char *split = ",; !";char *p2 = strtok(str,split);while( p2 != NULL ){ cout<<p2<<endl; p2 = strtok(NULL,split);}
11 指针和引用
11.1 指针
指针是一个变量,其值为另一个变量的地址。即内存位置的直接地址。
声明的一般形式:
- 数据类型是指针变量所指向的变量的数据类型,*表示其后的变量为指针变量
数据类型 *指针变量名;int *ip; //整型的指针 double *dp; //double 型的指针 float *fp; //浮点型的指针 char *ch; //字符型的指针
指针变量的初始化:
- &是取地址运算符,&变量名表示变量的地址。
- 变量的数据类型必须于指针变量的数据类型一致。
- 为了安全起见,有时会把指针初始化为空指针(NULL或0)
数据类型 *指针变量名 = &变量名;*指针变量名 = &变量名;int a;int *p = &a;int *p2;p2 = &a;
指针变量的引用:
- & 取地址符 * 指针运算符(间接运算符),其后是指针变量,表示该指针变量所指向的变量。
- & *的优先级是相同的,结合方式都是自左向右。比如 &*p等价于&(*p)。
int x = 3;int y;int *p;p = &x;y = *p;//y = a
指针运算(地址运算)
- 算术运算(移动指针运算):加减,自增自减。
- p+n运算得到的地址是p+n*sizeof(数据类型)。
- 两个相同数据类型的指针可以进行加减运算,一般用于数组的操作中。
- 关系运算:指针指向同一串连续存储单元才有意义,比如数组。与0比较,判断是不是空指针。
- 赋值运算:变量地址赋值给指针变量,数组元素地址赋值给指针变量,指针变量赋值给其他指针变量。
int arr[10],len;int *p1 = &arr[2],*p2 = &arr[5]; len = p2-p1;//arr[2] 和arr[5]之间的元素个数 3
new和delete运算符
- new-为变量分配内存空间;
- 可以通过判断new返回的指针的值,判断空间是否分配成功。
- delete-释放空间
指针变量 = new 数据类型(初值);delete 指针变量;delete[] 指针变量;//释放为多个变量分配的地址int *ip;ip= new int(1);delete ip;int *ip; ip= new int[10]; for (int i = 0; i < 10;i++) { ip[i] = i; } delete[] ip;int a[3][4] = {0};
指针与数组
- 数组名是数组的首地址,eg:arr为arr[0]的地址。
- 访问数组元素:arr[i],(arr+i),(p+i),p[i]
- 二维数组:arr+i == &arr[i],arr[i] == &arr[i][0] ,*(arr[i]+j) == arr[i][j]
- 指针访问二维数组:指向二维数组元素,指向一维数组
- 数组指针:
数据类型 (*指针变量名) [m]
int arr[10];int *p1 = arr;// *p1 = &arr[0];int a[3][5] = { 0 };int(*ap)[5];ap = a;ap+1;//表示下一个一维数组
指针与字符串
- 字符串数组名:
char ch[] = "heiren";char *p = ch;
- 字符串:
char *p = "heiren";
- 指针赋值运算:
char * p;p = "Heiren";
指针与函数,指针可以作为函数的参数,也可以作为函数的返回值。
11.2 引用
引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据,类似于window中的快捷方式。
- 引用不占内存空间,必须在定义的同时初始化,且不能再引用其他数据。
- 引用在定义时需要添加&,在使用时不能添加&,使用时添加&表示取地址
引用型变量声明:数据类型 &引用名 = 变量名;
int a;int &b = a;//a和b表示相同的变量,具有相同的地址。
引用可以作为函数参数,也可以作为函数返回值。
void swap(int &r1, int &r2) { int temp = r1; r1 = r2; r2 = temp;}int &add1(int &r) {r += 1;return r;}int main(){int a = 12;int b = add1(a);cout << a << " "<<b << endl;//13 13return 0;}
将引用作为函数返回值时不能返回局部数据的引用,因为当函数调用完成后局部数据就会被销毁。
函数在栈上运行,函数掉用完,后面的函数调用会覆盖之前函数的局部数据。
int &add1(int &r) {r += 1;int res = r;return res;}void test(){int xx = 123;int yy = 66;}int main(){int a = 12;int &b = add1(a);int &c = add1(a);test();//函数调用,覆盖之前函数的局部数据cout << a << " "<<b <<" "<< c<<endl;//14 -858993460 -858993460return 0;}
12 自定义数据类型
12.1 结构体
结构体可以包含不同数据类型的结构。
定义结构体的一般形式
struct 结构体类型名{成员类型1 成员名1;成员类型2 成员名2;... ...成员类型n 成员名n;};
结构体变量名的定义和初始化:
//定义结构体同时声明结构体变量名struct 结构体类型名{成员类型1 成员名1;成员类型2 成员名2;... ...成员类型n 成员名n;}变量名1,变量名2,...变量名n;//先定义结构体[struct] 结构体类型名 变量名;//直接定义struct {成员类型1 成员名1;成员类型2 成员名2;... ...成员类型n 成员名n;}变量名1,变量名2,...变量名n;struct person{int year;int age;string name;}p1 = {2019,24,"heiren"}, p1 = { 2020,24,"heiren" };struct person{int year;int age;string name;};struct person p1 = { 2019,24,"heiren" }, p1 = { 2020,24,"heiren" };struct {int year;int age;string name;}p1 = {2019,24,"heiren"}, p1 = { 2020,24,"heiren" };
结构体变量的使用:
- 具有相同类型的结构体变量可以进行赋值运算,但是不能输入输出
- 对结构体变量的成员引用:
结构体变量名.成员名
- 指向结构体的指针变量引用格式:
指针变量名->成员名;
结构体数组的定义,初始化和使用与结构体变量、基本类型数组相似
struct person{int year;int age;string name;}p[2] ={ {2019,24,"heiren"}, { 2020,24,"heiren" }};//可以不指定数组元素个数p[1].age;
结构体作为函数传递有三种:值传递,引用传递,指针传递
12.2 结构体大小和字节对齐
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐.
为什么需要字节对齐?各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。
三个个概念:
- 自身对齐值:数据类型本身的对齐值,结构体或类的的自身对齐值是其成员中最大的那个值,例如char类型的自身对齐值是1,short类型是2;
- 指定对齐值:编译器或程序员指定的对齐值,32位单片机的指定对齐值默认是4;
- 有效对齐值:自身对齐值和指定对齐值中较小的那个。
字节对齐的三个准则 - 结构体变量的首地址能够被其有效对齐值的大小所整除
- 结构体的总大小为结构体有效对齐值的整数倍。
- 结构体每个成员相对于结构体首地址的偏移量都是有效对齐值的整数倍。
可以通过#pragma pack(n)来设定变量以n字节对齐方式
举个例子
//指定对齐值=8struct st {// 空结构体大小1char c1;//1char c2;//2int i1;//8 int 起始地址按照字节对齐的原理应该是它长度4的整数倍char c3;//12short s4;//12 short 起始地址按照字节对齐的原理应该是它长度2的整数倍 12 + 2 = 12double d;//24 double 起始地址按照字节对齐的原理应该是它长度8的整数倍 12->16 + 8 = 24char c4;//32 24 + 4 = 28 结构体的总大小为8的整数倍 28->32int i2;//32 28+4 = 32int i3;//40 short s5;//40};cout << sizeof(st) << endl;//40//指定对齐值=4#pragma pack(4)struct st{//1 空结构体大小1char c1;//1char c2;//2int i1;//8char c3;//12short s4;//12double d;//20char c4;//24int i2;//28int i3;//32short s5;//36}s;cout << sizeof(st) << endl;//36
12.4 枚举(enum)和typedef声明
枚举已经在前面的章节介绍过,这里就不在赘述了。
typedef-为已存在的数据类型定义一个新的类型名称,不能定义变量。
typedef声明格式:typedef 类型名称 类型标识符;
typedef char *CP;typedef int INTEGER;
13 面向对象
13.1 类
- 类也是一种数据类型。
类的声明:
class 类名{public:公有数据成员;公有成员函数;private:私有数据成员;私有成员函数;protected:保护数据成员;保护成员函数;};
成员函数的定义:类内,类外,类外内联函数
//类外返回类型 类名:成员函数名(参数列表){函数体;}//内联函数:类外inline 返回类型 类名:成员函数名(参数列表){函数体;}
内联函数的代码会直接嵌入到主调函数中,可以节省调用时间,如果成员函数在类内定义,自动为内联函数。
13.2 类成员的访问权限以及类的封装
- 和Java、C#不同的是,C++中public、private、protected只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分
- 类内部没有访问权限的限制,都可以互相访问。
- 在C++中用class定义的类中,其成员的默认存取权限是private。
类外 | 派生类 | 类内 | |
---|---|---|---|
public | Y | Y | Y |
protected | N | Y | Y |
private | N | N | Y |
13.3 对象
//1.声明类同时定义对象class 类名{类体;}对象名列表;//2.先声明类,再定义对象类名 对象名(参数列表);//参数列表为空时,()可以不写//3. 不出现类名,直接定义对象class {类体;}对象名列表;//4.在堆上创建对象Person p(123, "yar");//在栈上创建对象Person *pp = new Person(234,"yar");//在堆上创建对象
注:不可以在定义类的同时对其数据成员进行初始化,因为类不是一个实体,不合法但是能编译运行
对象成员的引用:对象名.数据成员名 或者 对象名.成员函数名(参数列表)
13.4 构造函数
是一种特殊的成员函数,主要功能是为对象分配存储空间,以及为类成员变量赋初值
- 构造函数名必须与类名相同
- 没有任何返回值和返回类型
- 创建对象自动调用,不需要用户来调用,且只掉用一次
- 类没有定义任何构造函数,编译系统会自动为这个类生成一个默认的无参构造函数
构造函数定义
//1.类中定义 2.类中声明,类外定义[类名::]构造函数名(参数列表){函数体}
创建对象
类名 对象名(参数列表);//参数列表为空时,()可以不写
带默认参数的构造函数
class Person{public:Person(int = 0,string = "张三");void show();private:int age;string name;};Person::Person(int a, string s){cout<
带参数初始化表的构造函数
类名::构造函数名(参数列表):参数初始化表{函数体;}参数初始化列表的一般形式:参数名1(初值1),参数名2(初值2),...,参数名n(初值n)class Person{public:Person(int = 0,string = "张三");void show();private:int age;string name;};Person::Person(int a, string s):age(a),name(s){cout << a << " " << s << endl;}
构造函数重载:构造函数名字相同,参数个数和参数类型不一样。
class Person{public:Person();Person(int = 0,string = "张三");Person(double,string);void show();private:int age;double height;string name;};...
拷贝构造函数
类名::类名(类名&对象名){函数体;}class Person{public:Person(Person &p);//声明拷贝构造函数Person(int = 0,string = "张三");void show();private:int age;string name;};Person::Person(Person &p)//定义拷贝构造函数{cout << "拷贝构造函数" << endl;age = 0;name = "ABC";}Person::Person(int a, string s):age(a),name(s){cout << a << " " << s << endl;}int main(){Person p(123, "yar");Person p2(p);p2.show();return 0;}//输出123 yar拷贝构造函数age=0name=ABC
13.5 析构函数
是一种特殊的成员函数,当对象的生命周期结束时,用来释放分配给对象的内存空间爱你,并做一些清理的工作。
- 析构函数名与类名必须相同。
- 析构函数名前面必须加一个波浪号~。
- 没有参数,没有返回值,不能重载。
- 一个类中只能有一个析构函数。
- 没有定义析构函数,编译系统会自动为和这个类生成一个默认的析构函数。
析构函数的定义:
//1.类中定义 2.类中声明,类外定义[类名::]~析构函数名(){函数体;}
13.6 对象指针
对象指针的声明和使用
类名 *对象指针名;对象指针 = &对象名;//访问对象成员对象指针->数据成员名对象指针->成员函数名(参数列表)Person p(123, "yar");Person* pp = &p;Person* pp2 = new Person(234,"yar")pp->show();
指向对象成员的指针
数据成员类型 *指针变量名 = &对象名.数据成员名;函数类型 (类名::*指针变量名)(参数列表);指针变量名=&类名::成员函数名;(对象名.*指针变量名)(参数列表);Person p(123, "yar");void(Person::*pfun)();pfun = &Person::show;(p.*pfun)();
this指针
每个成员函数都有一个特殊的指针this,它始终指向当前被调用的成员函数操作的对象
class Person{public:Person(int = 0,string = "张三");void show();private:int age;string name;};Person::Person(int a, string s):age(a),name(s){cout << a << " " << s << endl;}void Person::show(){cout << "age="<age << endl;cout << "name=" <name << endl;}
13.7 静态成员
以关键字static开头的成员为静态成员,多个类共享。
- static 成员变量属于类,不属于某个具体的对象
- 静态成员函数只能访问类中静态数据成员
静态数据成员
//类内声明,类外定义class xxx{static 数据类型 静态数据成员名;}数据类型 类名::静态数据成员名=初值//访问类名::静态数据成员名;对象名.静态数据成员名;对象指针名->静态数据成员名;
静态成员函数
//类内声明,类外定义class xxx{static 返回值类型 静态成员函数名(参数列表);}返回值类型 类名::静态成员函数名(参数列表){函数体;}//访问类名::静态成员函数名(参数列表);对象名.静态成员函数名(参数列表);对象指针名->静态成员函数名(参数列表);
13.8 友元
借助友元(friend),可以使得其他类中得成员函数以及全局范围内得函数访问当前类得private成员。
友元函数
- 友元函数不是类的成员函数,所以没有this指针,必须通过参数传递对象。
- 友元函数中不能直接引用对象成员的名字,只能通过形参传递进来的对象或对象指针来引用该对象的成员。
//1.将非成员函数声明为友元函数class Person{public:Person(int = 0,string = "张三");friend void show(Person *pper);//将show声明为友元函数private:int age;string name;};Person::Person(int a, string s):age(a),name(s){cout << a << " " << s << endl;}void show(Person *pper){cout << "age="<age << endl;cout << "name=" <name << endl;}int main(){;Person *pp = new Person(234,"yar");show(pp);system("pause");return 0;}//2.将其他类的成员函数声明为友元函数//person中的成员函数可以访问MobilePhone中的私有成员变量class MobilePhone;//提前声明//声明Person类class Person{public:Person(int = 0,string = "张三");void show(MobilePhone *mp);private:int age;string name;};//声明MobilePhone类class MobilePhone{public:MobilePhone();friend void Person::show(MobilePhone *mp);private:int year;int memory;string name;};MobilePhone::MobilePhone(){year = 1;memory = 4;name = "iphone 6s";}Person::Person(int a, string s):age(a),name(s){cout << a << " " << s << endl;}void Person::show(MobilePhone *mp){cout <year << "年 " <memory << "G " <name <show(mp);system("pause");return 0;}
友元类
当一个类为另一个类的友元时,称这个类为友元类。友元类的所有成员函数都是另一个类中的友元成员。
语法形式:friend [class] 友元类名
- 类之间的友元关系不能传递
- 类之间的友元关系是单向的
- 友元关系不能被继承
class HardDisk{public:HardDisk();friend class Computer;private:int capacity;int speed;string brand;};HardDisk::HardDisk():capacity(128),speed(0),brand("三星"){}class Computer{public:Computer(HardDisk hd);void start();private:string userName;string name;int ram;string cpu;int osType;HardDisk hardDisk;};Computer::Computer(HardDisk hd):userName("yar"),name("YAR-PC"),ram(16),cpu("i7-4710"),osType(64){cout << "正在创建computer..." <hardDisk = hd;this->hardDisk.speed = 5400;cout << "硬盘转动...speed = " <hardDisk.speed << "转/分钟" << endl;}void Computer::start(){cout << hardDisk.brand << " " << hardDisk.capacity << "G" << hardDisk.speed << "转/分钟" << endl;cout << "笔记本开始运行..." << endl;}int main(){HardDisk hd;Computer cp(hd);cp.start();system("pause");return 0;}
13.9 类(class)与结构体(struct)的区别
- 引入C语言的结构体,是为了保证和c程序的兼容性。
- c语言中的结构体不允许定义函数成员,且没有访问控制权限的属性。
- c++为结构体引入了成员函数,访问控制权限,继承,多态等面向对象特性。
- c语言中,空结构体的大小为0,而C++中空结构体大小为1。
- class中成员默认是private,struct中的成员默认是public。
- class继承默认是private继承,而struct继承默认是public继承。
- class可以使用模版,而struct不能。
举个例子:
//结构体默认权限为publicstruct person{void show();string name;int age;};int main(){person p;p.name = "heiren";p.age = 666;p.show();cout <<"name="<< p.name <<" age="<< p.age << endl;system("pause");return 0;}
将struct改为class,运行报错。
14 继承和派生
14.1 继承和派生概述
继承就是再一个已有类的基础上建立一个新类,已有的类称基类或父类,新建立的类称为派生类和子类;派生和继承是一个概念,角度不同而已,继承是儿子继承父亲的产业,派生是父亲把产业传承给儿子。
- 一个基类可以派生出多个派生类,一个派生类可以继承多个基类
派生类的声明:
//继承方式为可选项,默认为private,还有public,protectedclass 派生类名:[继承方式]基类名{派生类新增加的成员声明;};
继承方式:
- public-基类的public成员和protected成员的访问属性保持不变,私有成员不可见。
- private-基类的public成员和protected成员成为private成员,只能被派生类的成员函数直接访问,私有成员不可见。
- protected-基类的public成员和protected成员成为protected成员,只能被派生类的成员函数直接访问,私有成员不可见。
继承方式/基类成员 | public成员 | protected成员 | private成员 |
---|---|---|---|
public | public | protected | 不可见 |
protected | protected | protected | 不可见 |
private | private | private | 不可见 |
- 利用using关键字可以改变基类成员再派生类中的访问权限;using只能修改基类中public和protected成员的访问权限。
class Base{public:void show();protected:int aa;double dd;};void Base::show(){}class Person:public Base{public:using Base::aa;//将基类的protected成员变成publicusing Base::dd;//将基类的protected成员变成publicprivate:using Base::show;//将基类的public成员变成privatestring name;};int main(){Person *p = new Person();p->aa = 12;p->dd = 12.3;p->show();//出错delete p;return 0;}
派生类的构造函数和析构函数
- 先执行基类的构造函数,随后执行派生类的构造函数
- 先执行派生类的析构函数,再执行基类的析构函数。
- 派生类的构造函数:
派生类名(总参数列表):基类名(基类参数列表),子对象名1(参数列表){构造函数体;}
class Base{public:Base(int, double);~Base();private:int aa;double dd;};Base::Base(int a, double d) :aa(a), dd(d){cout << "Base Class 构造函数!!!" << endl;}Base::~Base(){cout << "Base Class 析构函数!!!" << endl;}class Person:public Base{public:Person(int,double,string);~Person();private:string name;};Person::Person(int a,double d,string str):Base(a,d),name(str){cout << "Person Class 构造函数!!!" << endl;}Person::~Person(){cout << "Person Class 析构函数!!!" << endl;}int main(){cout << "创建Person对象..." << endl;Person *p = new Person(1,2,"yar");cout << "删除Person对象...." << endl;delete p;system("pause");return 0;}
14.2 多继承
一个派生类同时继承多个基类的行为。
多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。
多重继承派生类声明的一般形式:
class 派生类名:继承方式1 基类1,继承方式2 基类2{派生类主体;};
多重继承派生类的构造函数:
派生类名(总参数列表):基类名1(基类参数列表1),基类名2(基类参数列表2),子对象名1,...(参数列表){构造函数体;}`
二义性问题:多个基类中有同名成员,出现访问不唯一的问题。
- 1.
类名::同名成员名;
- 2.派生类定义同名成员,访问的就是派生类同名成员。
14.3 虚基类
c++引入虚基类使得派生类再继承间接共同基类时只保留一份同名成员。
- 虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class)。
- 派生类的 同名成员 比虚基类的 优先级更高
虚基类的声明:class 派生类名:virtual 继承方式 基类名
class A//虚基类{protected:int a;};class B: virtual public A{protected:int b;};class C:virtual public A{protected:int c;};class D:public B,public C{protected:int d;void show(){b = 123;c = 23;a = 1;}};
- 如果 B 或 C 其中的一个类定义了a,也不会有二义性,派生类的a 比虚基类的a 优先级更高。
- 如果 B 和 C 中都定义了 a,那么D直接访问a 将产生二义性问题。
应用:c++中的iostream , istream , ostream,base_io
15 多态和虚函数
15.1 向上转型
数据类型的转换,编译器会将小数部分直接丢掉(不是四舍五入)
int a = 66.9;printf("%d\n", a);//66float b = 66;printf("%f\n", b);//66.000000
- 只能将将派生类赋值给基类(C++中称为向上转型): 派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用
- 派生类对象赋值给基类对象,舍弃派生类新增的成员;派生类指针赋值给基类指针,没有拷贝对象的成员,也没有修改对象本身的数据,仅仅是改变了指针的指向;派生类引用赋值给基类引用,和指针的一样。、
上转型后通过基类的对象、指针、引用只能访问从基类继承过去的成员(包括成员变量和成员函数),不能访问派生类新增的成员
15.2 多态
不同的对象可以使用同一个函数名调用不同内容的函数。
- 静态多态性-在程序编译时系统就决定调用哪个函数,比如函数重载和静态多态性
- 动态多态性-在程序运行过程中动态确定调用那个函数,通过虚函数实现的。
15.3 虚函数
实现程序多态性的一个重要手段,使用基类对象指针访问派生类对象的同名函数。
- 将基类中的函数声明为虚函数,派生类中的同名函数自动为虚函数。
- 声明形式:
virtual 函数类型 函数名 (参数列表);
- 构造函数不能声明为虚函数,析构函数可以声明为虚函数。
class A{public:virtual void show(){cout <show();//B show 如果show方法前没用virtual声明为虚函数,这里会输出A showsystem("pause");return 0;}
15.4 纯虚函数
在基类中不执行具体的操作,只为派生类提供统一结构的虚函数,将其声明为虚函数。
class A{public:virtual void show() = 0;};class B: public A{public:void show(){cout << "B show" << endl;}};
抽象类:包含纯虚函数的类称为抽象类。由于纯虚函数不能被调用,所以不能利用抽象类创建对象,又称抽象基类。
16 运算符重载
所谓重载,就是赋予新的含义。函数重载(Function Overloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作。运算符重载(Operator Overloading)也是一个道理,同一个运算符可以有不同的功能。
- 运算符重载是通过函数实现的,它本质上是函数重载。
允许重载的运算符
运算符名称 | 运算符 |
---|---|
双目算术运算符 | +、-、*、、、% |
关系运算符 | ==、!=、、= |
逻辑运算符 | |
单目运算符 | +、-、*(指针)、&(取地址) |
自增自减运算符 | ++、– |
位运算符 | |、&、-、……、<> |
赋值运算符 | =、+=、-=、*=、/=、%=、&=、!=、^=、<>= |
空间分配和释放 | new、delete、new[]、delete[] |
其他运算符 | ()(函数调用) 、->(成员访问)、->*(成员指针访问)、,(逗号)、 |
- 不允许重载的运算符
运算符名称 | 运算符 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
成员访问运算符 | . | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
成员指针访问运算符 | . * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
域运算符 | :: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
长度运算符 | sizeof() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
条件运算符 | ” />16.2 形式运算符重载的形式有两种:重载函数作为类的成员,重载函数作为类的友元函数
16.3 常用运算符的重载1.自增自减:
2.赋值运算符:
3.输入\输出运算符重载
16.4 实现类型转换
17 IO流流-一连串连续不断的数据集合。 17.1 流类和对象
输入输出流程:键盘输入=》键盘缓冲区=(回车触发)》程序的输入缓冲区=》‘>>’提取数据
|
0) R = mid – 1;else L = mid + 1;} while (L <= R);ioFile.close();system(“pause”);return 0;}
18.6 文本文件和二进制文件打开方式的区别
- UNIX/Linux 平台中,用文本方式或二进制方式打开文件没有任何区别。
- 在 UNIX/Linux 平台中,文本文件以\n(ASCII 码为 0x0a)作为换行符号;而在 Windows 平台中,文本文件以连在一起的\r\n(\r的 ASCII 码是 0x0d)作为换行符号。
- 在 Windows 平台中,如果以文本方式打开文件,当读取文件时,系统会将文件中所有的\r\n转换成一个字符\n,如果文件中有连续的两个字节是 0x0d0a,则系统会丢弃前面的 0x0d 这个字节,只读入 0x0a。当写入文件时,系统会将\n转换成\r\n写入。
用二进制方式打开文件总是最保险的。
19 泛型和模板
- 泛型程序设计在实现时不指定具体要操作的数据的类型的程序设计方法的一种算法,指的是算法只要实现一遍,就能适用于多种数据类型,优势在于代码复用,减少重复代码的编写。
- 模板是泛型的基础,是创建泛型类或函数的蓝图或公式。
19.1 函数模板
函数模板的一般形式:
template或template函数类型 函数名(参数列表){函数体;}template//class可以换成typename函数类型 函数名(参数列表){函数体;}//举个栗子template T max(T a, T b){return a > b ? a : b;}int main(){cout <<"max value is "<< max(12,34) << endl;//34cout << "max value is " << max(12.4, 13.6) << endl;//13.6cout << "max value is " << max(12.4, 13) << endl;//error 没有与参数列表匹配的 函数模板 "max" 实例参数类型为:(double, int)return 0;}
19.2 类模板
- 声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。
类模板的一般形式:
template//class可以换成typename 模板头class 类名{函数定义;};//多个类型参数和函数模板类似,逗号隔开
当类中的成员函数在类的声明之外定义时,它必须定义为函数模板,带上模板头,定义形式如下:
template//class可以换成typename函数类型 类名::函数名(形参列表){函数体;}
举个栗子:
template // 模板头 没有分号class Point {public:Point(T1 x, T2 y) : x(x), y(y) { }public:T1 getX() const; //成员函数后加const,声明该函数内部不会改变成员变量的值void setX(T1 x); T2 getY() const; void setY(T2 y); private:T1 x; T2 y; };template //模板头T1 Point::getX() const {return x;}templatevoid Point::setX(T1 x) {x = x;}templateT2 Point::getY() const {return y;}templatevoid Point::setY(T2 y) {y = y;}int main(){Point p1(66, 20.5);Point p2(10, "东经33度");Point *p3 = new Point("西经12度", "北纬66度");cout << "x=" << p1.getX() << ", y=" << p1.getY() << endl;cout << "x=" << p2.getX() << ", y=" << p2.getY() << endl;cout << "x=" <getX() << ", y=" <getY() << endl;return 0;}
19.3 typename 和 class 的区别
在模板引入 c++ 后,采用class来定义模板参数类型,后来为了避免 class 在声明类和模板的使用可能给人带来混淆,所以引入了 typename 这个关键字。
- 模板定义语法中关键字 class 与 typename 的作用完全一样。
- 不同的是typename 还有另外一个作用为:使用嵌套依赖类型(nested depended name)
class MyClass{public:typedef int LengthType;LengthType getLength() const{return this->length;}void setLength(LengthType length){this->length = length;}private:LengthType length;};templatevoid MyMethod(T myclass){//告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量typedef typename T::LengthType LengthType; //LengthType length = myclass.getLength();cout << "length = " <<length<< endl;}int main(){MyClass my;my.setLength(666);MyMethod(my);//length = 666return 0;}
19.4 强弱类型语言和c++模板的那点猫腻
计算机编程语言可以根据在“定义变量时是否要显式地指明数据类型“可以分为强类型语言和弱类型语言。
- 强类型语言-在定义变量时需要显式地指明数据类型,为变量指明某种数据类型后就不能赋予其他类型的数据了,除非经过强制类型转换或隐式类型转换。典型的强类型语言有 C/C++、Java、C# 等。
int a = 123; //不转换a = 12.89; //隐式转换 12(舍去小数部分)a = (int)"heiren,HelloWorld"; //强制转换(得到字符串的地址) 不同类型之间转换需要强制
//Java 对类型转换的要求比 C/C++ 更为严格,隐式转换只允许由低向高转,由高向低转必须强制转换。int a = 100; //不转换a = (int)12.34; //强制转换(直接舍去小数部分,得到12)
- 弱类型语言-在定义变量时不需要显式地指明数据类型,编译器(解释器)会根据赋给变量的数据自动推导出类型,并且可以赋给变量不同类型的数据。典型的弱类型语言有 JavaScript、Python、PHP、Ruby、Shell、Perl 等。
var a = 100; //赋给整数a = 12.34; //赋给小数a = "heiren,HelloWorld"; //赋给字符串a = new Array("JavaScript","React","JSON"); //赋给数组
**强类型语言在编译期间就能检测某个变量的操作是否正确,因为变量的类型始终哦都市确定的,加快了程序的运行;对于弱类型的语言,变量的类型可以随时改变,编译器在编译期间能确定变量的类型,只有等到程序运行后、赋值后才能确定变量当前是什么类型,所以传统的编译对弱类型语言意义不大。**
- 解释型语言-弱类型往往是解释型语言,边执行边编译
- 编译型语言-先编译后执行。
强类型语言较为严谨,在编译时就能发现很多错误,适合开发大型的、系统级的、工业级的项目;而弱类型语言较为灵活,编码效率高,部署容易,学习成本低,在 Web 开发中大显身手。另外,强类型语言的 IDE 一般都比较强大,代码感知能力好,提示信息丰富;而弱类型语言一般都是在编辑器中直接书写代码。
C++模板退出的动力来源是对数据结构的封装:数据结构关注的是数据的存储以及对其的增删改查操作,C++开发者们想封装这些结构,但是这些结构中数据成分的类型无法提前预测,于是模板诞生了。
STL(Standard Template Library,标准模板库)就是c++对数据结构封装后的称呼。
20 命名空间和异常处理
20.1 命名空间
命名空间实际上是由用户自己命名的一块内存区域,用户可以根据需要指定一个有名字的空间区域,每个命名空间都有一个作用域,将一些全局实体放在该命名空间中,就与其他全局实体分割开来。
命名空间定义的一般形式:
namespace [命名空间名]//名省略时,表示无名的命名空间{命名空间成员;}
命名空间成员的引用:命名空间名::命名空间成员名
使用命名空间别名:namespace 别名 = 命名空间名
使用using声明命名空间成员的格式:using 命名空间名::命名空间成员名;
使用using声明命名空间的全部成员:using namespace 命名空间名;
- using声明后,在using语句所在的作用域中使用该命名空间成员时,不必再用命名空间名加以限定。
- 标准C++库的所有标识符(包括函数、类、对象和类模板)都是在一个名为std的命名空间中定义的。
- 无名的命名空间,只在本文件的作用域内有效。
20.2 异常处理
异常就是程序在执行过程中,由于使用环境变化和用户操作等原因产生的错误,从而影响程序的运行。
- 程序中常见的错误:语法错误,运行错误
- 异常处理机制的组成:检查异常(try)、抛出异常(throw)、捕获并处理异常(catch)
异常处理语句:
- 被检查的语句必须放在try后面的{}中,否则不起作用,{}不能省略。
- 进行异常处理的语句必须放在catch后面的{}中,catch后()中的异常信息类型不能省略,变量名可以省略。
- catch语句块不能单独使用,必须和try语句块作为整体出现。
- 在try-catch结构中,只能有一个try,但可以有多个catch.
- catch(…)通常放在最后,可以捕获任何类型的异常信息。
try{被检查的语句(可以有多个throw语句);}catch(异常信息类型 [变量名]){ }
throw语句:thow 变量或表达式;
- throw放在try中,也可以单独使用,向上一层函数寻找try-catch,没有找到系统就会调用系统函数terminate,使程序终止运行。
try{throw 123;}catch (int a){cout << a << endl;//123}
c++中没有finally
类的异常处理,当在try中定义了类对象,在try中抛出异常前创建的对象将被自动释放。
异常规范-描述了一个函数允许抛出那些异常类型。
- 异常规范应同时出现在函数声明和函数定义中。
- 如果没有异常规范,可以抛出任何类型的异常。
异常规范的一般形式:函数类型 函数名(参数类型)throw ([异常类型1,异常类型2,...])
float fun(float float)throw(int,float,double);
C++标准异常