既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
- for
for(init;conditon;increment)//0为false,非0或什么也不写为true
{
statement(s);
}
1.init首先被执,且只会执行一次,也可以不写任何语句。
2.然后会判断conditon,true执行循环主体,false跳过循环
3.执行完循环主体,执行increment,跳到2
int array[5] = { 11, 22, 33, 44, 55 };
for (int x : array)
{
cout << x << " ";
}
cout << endl;
// auto 类型也是 C++11 新标准中的,用来自动获取变量的类型
for (auto x : array)
{
cout << x << " ";
}
- 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<filename> 或者#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];//所有元素为0
static int arr3[3];
int arr4[4];
cout << "arr1:"<<arr1[0] << arr1[1] << arr1[2] << arr1[3] << endl;
cout << "arr2:" << arr2[0] << arr2[1] << arr2[2] << arr2[3] << endl;
cout << "arr3:" << arr3[0] << arr3[1] << arr3[2] << arr3[3] << endl;
cout << "arr4:" << arr4[0] << arr4[1] << arr4[2] << arr4[3] << endl;
8.2二维数组
定义一维数组的形式:数据类型 数据名[常量表达式1][常量表达式2]
初始化的形式:数据类型 数组名[常量表达式1] [常量表达式2]= {初值表};
为数组的某一个元素赋值:数组名[行下标][列下标] =值
(下标从0开始)
数组的引用:数组名[行下标][列下标]
- 将所有数据写在一个花括号内,自动按照数组元素个数在内存中排列的顺序赋值
- 可对部分元素赋值,其余元素的值自动取0.
- 定义初始化数组时,可以省略第一维的长度,第二维不能省,系统会自动确认行数
int arr1[2][3];
int arr[2][3] = {0];//所有元素为0
int 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 };
字符数组
char类型的数组,在字符数组中最后一位为’\0’)时,可以看成时字符串。在C++中定义了string类,在Visual C++中定义了Cstring类。
字符串中每一个字符占用一个字节,再加上最后一个空字符。如:
//字符串长度为8个字节,最后一位是'\0'。
char array[10] = "yuanrui";//yuanrui\0\0\0
//也可以不用定义字符串长度,如:
char arr[] = "yuanrui";//yuanrui\0
8.3 指向数组的指针
指针的概念会在后面详细讲解。
double \*p;
double arr[10];
p = arr;//p = &arr[0];
\*(p+3);//arr[3]
8.4 数组与new(动态创建数组)
一维数组:
int\* arr1 = new int[2];//delete []arr1;
int\* arr2 = new int[3]{ 1,2 };//delete []arr2
二维数组
int m=2, n=3;
int\*\* arr3 = new int\*[2];//delete []arr3
for (int i = 0; i < 10; ++i)
{
arr3[i] = new int[3]; // delete []arr3[i]
}
int\* arr4 = new int[m\*n];//数据按行存储 delete []arr3
8.5 数组与函数
数组->函数
- 如果传递二维数组,形参必须制定第二维的长度。
形式参数是一个指针:void function(int *param)
形式参数是一个已定义大小的数组:void function(int param[10])
形式参数是一个未定义大小的数组:void function(int param[])
二维数组:void function(int a[][3],int size)
函数返回数组
- C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
int \* function();
int\*\* function();
8.6 获取数组的大小
- 动态创建(new)的基本数据类型数组无法取得数组大小
int a[3];
//第一种方法
cout<<sizeof(a)/sizeof(a[0])<<endl;
//第二种方法
cout << end(a) - begin(a) << endl;
//二维数组
int arr[5][3];
int lines = sizeof(arr) / sizeof(arr[0][0]);
int row = sizeof(arr) / sizeof(arr[0]);//行
int col = lines / row;//列
cout << row << "::"<<col << endl;
cout << end(arr) - begin(arr) << endl;//5行
9 函数
函数是实现模块化程序设计思想的重要工具, C++程序中每一项操作基本都是由一个函数来实现的,C++程序中只能有一个主函数(main)
9.1 函数声明与定义
- 函数类型-函数的返回值类型;函数名-必须符合C++标识符命名规则,后面必须跟一对括号;函数体-实现函数功能的主题部分;参数列表-函数名后面的括号内,用于向函数传递数值或带回数值。
- 函数声明中,参数名可以省略,参数类型和函数的类型不能省略。
- 函数声明可以放在主调函数内部,放在调用语句之前;也可以放在主调函数外,如果位于所有定义函数之前,后面函数定义顺序任意,各个主调函数调用也不必再做声明
- 当函数定义在前,函数调用灾后,可以不用函数声明。
后两条总结一下就是:调用函数前,程序得知道有这个函数,声明就是提前让程序知道有这么的玩意
函数声明:
函数类型 函数名(参数列表);
eg:
int max(int a,int b);//声明函数时,a,b可以省略
int max(int,int);
void show();
函数定义:
函数类型 函数名(参数列表)
{
函数体;
}
eg:
int max(int a,int b)
{
int z;
z = a>b?a:b;
return z;
}
9.2 函数的参数与返回值
- 形参:函数定义后面括号里的参数,函数调用前不占内存。
- 实参:函数调用括号里的参数,可以是常量,变量或表达式等。
形参和实参必须个数相同、类型一致,顺序一致
函数传递方式:传值,指针,引用
关于指针和引用后面有详细介绍。
//传值-修改函数内的形式参数对实际参数没有影响
int add(int value)
{
value++;
return value;
}
int main()
{
int v = 10;
cout << "add() = " << add(v) << endl;//add() = 11
cout << "v = " << v << endl;//v = 10
return 0;
}
//指针-修改形式参数会影响实际参数
int add(int\* pValue)
{
(\*pValue)++;
return \*pValue;
}
int main()
{
int v = 10;
cout << "add() = " << add(&v) << endl;//add() = 11
cout << "v = " << v << endl;//v = 11
return 0;
}
//引用-修改形式参数会影响实际参数
int add(int &value)
{
value++;
return value;
}
int main()
{
int v = 10;
cout << "add() = " << add(v) << endl;//add() = 11
cout << "v = " << v << endl;//v = 11
return 0;
}
有默认值参数的函数
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;//888
return 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 = 55
system("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控制台程序,点击项目->项目属性设置内联函数优化
2.编写内联函数代码,设置断点,debug启动
#include <iostream>
#include <string>
using namespace std;
inline int add(int a, int b)
{
return a + b;//断点1
}
int main()
{
int result = add(12, 34);
cout << result << endl;//断点2
return 0;
}
3.调试->窗口->反汇编,然后就能看到编译后的汇编程序
...
int result = add(12, 34);
00B620DE mov eax,0Ch
00B620E3 add eax,22h //对eax中和22h中值进行相加,赋值给eax
00B620E6 mov dword ptr [result],eax
cout << result << endl;
00B620E9 mov esi,esp
00B620EB push offset std::endl<char,std::char_traits<char> > (0B610A5h)
00B620F0 mov edi,esp
00B620F2 mov eax,dword ptr [result]
00B620F5 push eax
00B620F6 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0B6D098h)]
00B620FC call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6D0A8h)]
00B62102 cmp edi,esp
00B62104 call __RTC_CheckEsp (0B611C7h)
00B62109 mov ecx,eax
00B6210B call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6D0ACh)]
00B62111 cmp esi,esp
00B62113 call __RTC_CheckEsp (0B611C7h)
return 0;
4.从汇编代码中可以代码编译后内联函数直接嵌入到主函数中,并且断点1不会执行到,下面是没使用内联函数(去掉inline关键字)的汇编代码:
int result = add(12, 34);
00291A4E push 22h
00291A50 push 0Ch
00291A52 call add (02914D8h) //调用add函数
00291A57 add esp,8//移动堆栈指针esp,继续执行主函数
00291A5A mov dword ptr [result],eax
cout << result << endl;
00291A5D mov esi,esp
00291A5F push offset std::endl<char,std::char_traits<char> > (02910A5h)
00291A64 mov edi,esp
00291A66 mov eax,dword ptr [result]
00291A69 push eax
00291A6A mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (029D098h)]
cout << result << endl;
00291A70 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (029D0A8h)]
00291A76 cmp edi,esp
00291A78 call __RTC_CheckEsp (02911C7h)
00291A7D mov ecx,eax
00291A7F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (029D0ACh)]
00291A85 cmp esi,esp
00291A87 call __RTC_CheckEsp (02911C7h)
system("pause");
00291A8C mov esi,esp
00291A8E push offset string "pause" (0299B30h)
00291A93 call dword ptr [__imp__system (029D1DCh)]
00291A99 add esp,4
00291A9C cmp esi,esp
00291A9E call __RTC_CheckEsp (02911C7h)
return 0;
从以上代码代码可以看出,在主函数中调用(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<char> > (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<char> >::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<char> >::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,否则返回-1
strlen(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 <iostream>
#include <algorithm>
#include <string>
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<”hello”)。 使用>,>=,<,<=这些操作符的时候是根据“当前字符特性”将字符按字典顺序进行逐一得 比较,string (“aaaa”) <string(aaaaa)。
compare();//支持多参数处理,支持用索引值和长度定位子串来进行比较。返回一个整数来表示比较结果,返回值意义如下:0:相等 1:大于 -1:
push\_back()
insert( size_type index, size_type count, CharT ch );//在index位置插入count个字符ch
insert( 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函数如果未找到指定字符,返回的就是一个npos
iterator insert( iterator pos, CharT ch );
iterator insert( const_iterator pos, CharT ch );
void insert( iterator pos, size_type n, CharT ch );//迭代器指向的pos位置插入n个字符ch
iterator insert( const_iterator pos, size_type count, CharT ch );//迭代器指向的pos位置插入count个字符ch
void 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个字符,替换成字符串s
string& replace(size_t pos, size_t n, size_t n1, char c); //将当前字符串从pos索引开始的n个字符,替换成n1个字符c
string& 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 13
return 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 -858993460
return 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字节对齐方式
举个例子
//指定对齐值=8
struct st
{
// 空结构体大小1
char c1;//1
char c2;//2
int i1;//8 int 起始地址按照字节对齐的原理应该是它长度4的整数倍
char c3;//12
short s4;//12 short 起始地址按照字节对齐的原理应该是它长度2的整数倍 12 + 2 = 12
double d;//24 double 起始地址按照字节对齐的原理应该是它长度8的整数倍 12->16 + 8 = 24
char c4;//32 24 + 4 = 28 结构体的总大小为8的整数倍 28->32
int i2;//32 28+4 = 32
int i3;//40
short s5;//40
};
cout << sizeof(st) << endl;//40
//指定对齐值=4
#pragma pack(4)
struct st
{
//1 空结构体大小1
char c1;//1
char c2;//2
int i1;//8
char c3;//12
short s4;//12
double d;//20
char c4;//24
int i2;//28
int i3;//32
short s5;//36
}s;
cout << sizeof(st) << endl;//36
12.3 公用体(union)
几个不同的变量共享同一个地址开始的内存空间。
- 成员类型可以是基本数据类型,也可以是构造数据类型。
- 公用体变量初始化时,只能对第一个成员赋值。
- 公用体变量所占的内存长度等于最长的成员长度。
- 公用体变量在一个时刻只能一个成员发挥作用,赋值时,成员之间会互相覆盖,最后一次被赋值的成员起作用。
定义
union 共同体类型名
{
成员类型1 成员名1;
成员类型2 成员名2;
... ...
成员类型n 成员名n;
};
初始化
union data
{
int i;
float f;
char c;
}x = {123};
union data
{
float f;
int i;
char c;
};
data x = {12.3};
union
{
char c;
int i;
float f;
}x = {’y‘};
引用
共同体变量名.成员名;
union data
{
int i;
float f;
char c;
}x = {12};
int main()
{
cout << x.i << " " << x.f << " " << x.c << endl;//12 1.68156e-44
x.c = 'c';
cout << x.i <<" "<< x.f << " " << x.c << endl;//99 1.38729e-43 c
return 0;
}
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<<a<<" "<<s<<endl;
age = a;
name = s;
}
void Person::show()
{
cout << "age="<<age << endl;
cout << "name=" <<name << endl;
}
int main()
{
Person p; //0 张三
Person p2(12);//12 张三
Person p3(123, "yar");//123 yar
return 0;
}
带参数初始化表的构造函数
类名::构造函数名(参数列表):参数初始化表
{
函数体;
}
参数初始化列表的一般形式:
参数名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=0
name=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="<<this->age << endl;
cout << "name=" <<this->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="<< pper->age << endl;
cout << "name=" << pper->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 << mp->year << "年 " << mp->memory << "G " << mp->name << endl;
}
int main()
{
Person \*pp = new Person(234,"yar");
MobilePhone \*mp = new MobilePhone;
pp->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..." << endl;
this->hardDisk = hd;
this->hardDisk.speed = 5400;
cout << "硬盘转动...speed = " << this->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不能。
举个例子:
//结构体默认权限为public
struct 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,protected
class 派生类名:[继承方式]基类名
{
派生类新增加的成员声明;
};
继承方式:
- 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成员变成public
using Base::dd;//将基类的protected成员变成public
private:
using Base::show;//将基类的public成员变成private
string 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);//66
float b = 66;
printf("%f\n", b);//66.000000
- 只能将将派生类赋值给基类(C++中称为向上转型): 派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用
- 派生类对象赋值给基类对象,舍弃派生类新增的成员;派生类指针赋值给基类指针,没有拷贝对象的成员,也没有修改对象本身的数据,仅仅是改变了指针的指向;派生类引用赋值给基类引用,和指针的一样。、
上转型后通过基类的对象、指针、引用只能访问从基类继承过去的成员(包括成员变量和成员函数),不能访问派生类新增的成员
15.2 多态
不同的对象可以使用同一个函数名调用不同内容的函数。
- 静态多态性-在程序编译时系统就决定调用哪个函数,比如函数重载和静态多态性
- 动态多态性-在程序运行过程中动态确定调用那个函数,通过虚函数实现的。
15.3 虚函数
实现程序多态性的一个重要手段,使用基类对象指针访问派生类对象的同名函数。
- 将基类中的函数声明为虚函数,派生类中的同名函数自动为虚函数。
- 声明形式:
virtual 函数类型 函数名 (参数列表);
- 构造函数不能声明为虚函数,析构函数可以声明为虚函数。
class A
{
public:
virtual void show()
{
cout << "A show" << endl;
}
};
class B: public A
{
public:
void show()
{
cout << "B show" << endl;
}
};
int main()
{
B b;
b.show();//B show
A \*pA = &b;
pA->show();//B show 如果show方法前没用virtual声明为虚函数,这里会输出A show
system("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.1 定义
重载运算符遵循的规则:
- 不可以自己定义新的运算符,只能对已有的C++运算符重载。
- 不能改变运算符运算对象的个数。
- 不能改变运算符的优先级和结合性
- 应与标准类型运算功能相似,避免影响可读性。
一般格式:
函数类型 operator运算符(参数列表)
{
函数体
}
//举个栗子:定义一个向量类,通过运算符重载,可以用+进行运算。
class Vector3
{
public:
Vector3();
Vector3(double x,double y,double z);
public:
Vector3 operator+(const Vector3 &A)const;
void display()const;
private:
double m_x;
double m_y;
double m_z;
};
Vector3::Vector3() :m\_x(0.0), m\_y(0.0), m\_z(0.0) {}
Vector3::Vector3(double x, double y,double z) : m\_x(x), m\_y(y), m\_z(z) {}
//运算符重载
Vector3 Vector3::operator+(const Vector3 &A) const
{
Vector3 B;
B.m_x = this->m_x + A.m_x;
B.m_y = this->m_y + A.m_y;
B.m_z = this->m_z + A.m_z;
return B;
}
void Vector3::display()const
{
cout<<"(" << m_x << "," << m_y << "," << m_z << ")" << endl;
}
16.2 形式
运算符重载的形式有两种:重载函数作为类的成员,重载函数作为类的友元函数
根据运算符操作数的不同:双目运算符作为类成员函数,单目运算符作为类的成员函数,双目运算符作为类的友员函数,单目运算符作为类的友元函数。
- 双目运算符作为友元函数时需要制定两个参数。
- 运算符重载函数作为类成员函数可以显式调用。
class Vector3
{
public:
Vector3();
Vector3(double x,double y,double z);
public:
Vector3 operator+(const Vector3 &A)const;
Vector3 operator++();
friend Vector3 operator-(const Vector3 &v1, const Vector3 &v2);
friend Vector3 operator--(Vector3 &v);
void display()const;
private:
double m_x;
double m_y;
double m_z;
};
Vector3::Vector3() :m\_x(0.0), m\_y(0.0), m\_z(0.0) {}
Vector3::Vector3(double x, double y,double z) : m\_x(x), m\_y(y), m\_z(z) {}
//运算符重载
Vector3 Vector3::operator+(const Vector3 &A) const
{
Vector3 B;
B.m_x = this->m_x + A.m_x;
B.m_y = this->m_y + A.m_y;
B.m_z = this->m_z + A.m_z;
return B;
}
Vector3 Vector3::operator++()
{
this->m_x ++;
this->m_y ++;
this->m_z ++;
return \*this;
}
void Vector3::display()const
{
cout<<"(" << m_x << "," << m_y << "," << m_z << ")" << endl;
}
Vector3 operator-(const Vector3 &v1,const Vector3 &v2)
{
Vector3 B(v1.m_x - v2.m_x, v1.m_y - v2.m_y, v1.m_z - v2.m_z);
return B;
}
Vector3 operator--( Vector3 &v)
{
v.m_x--;
v.m_y--;
v.m_z --;
return v;
}
int main()
{
Vector3 v1(1, 2, 3);
Vector3 v2(2, 3, 2);
++v1;//v1.operator++(); 作为类成员函数可以显式调用
v1.display();
--v2;
v2.display();
![img](https://img-blog.csdnimg.cn/img_convert/c0d858b220f7f9ee026531d19447d82e.png)
![img](https://img-blog.csdnimg.cn/img_convert/811d2056dc2481f5478b91fde239fdfe.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
函数类型 operator运算符(参数列表)
{
函数体
}
//举个栗子:定义一个向量类,通过运算符重载,可以用+进行运算。
class Vector3
{
public:
Vector3();
Vector3(double x,double y,double z);
public:
Vector3 operator+(const Vector3 &A)const;
void display()const;
private:
double m_x;
double m_y;
double m_z;
};
Vector3::Vector3() :m\_x(0.0), m\_y(0.0), m\_z(0.0) {}
Vector3::Vector3(double x, double y,double z) : m\_x(x), m\_y(y), m\_z(z) {}
//运算符重载
Vector3 Vector3::operator+(const Vector3 &A) const
{
Vector3 B;
B.m_x = this->m_x + A.m_x;
B.m_y = this->m_y + A.m_y;
B.m_z = this->m_z + A.m_z;
return B;
}
void Vector3::display()const
{
cout<<"(" << m_x << "," << m_y << "," << m_z << ")" << endl;
}
16.2 形式
运算符重载的形式有两种:重载函数作为类的成员,重载函数作为类的友元函数
根据运算符操作数的不同:双目运算符作为类成员函数,单目运算符作为类的成员函数,双目运算符作为类的友员函数,单目运算符作为类的友元函数。
- 双目运算符作为友元函数时需要制定两个参数。
- 运算符重载函数作为类成员函数可以显式调用。
class Vector3
{
public:
Vector3();
Vector3(double x,double y,double z);
public:
Vector3 operator+(const Vector3 &A)const;
Vector3 operator++();
friend Vector3 operator-(const Vector3 &v1, const Vector3 &v2);
friend Vector3 operator--(Vector3 &v);
void display()const;
private:
double m_x;
double m_y;
double m_z;
};
Vector3::Vector3() :m\_x(0.0), m\_y(0.0), m\_z(0.0) {}
Vector3::Vector3(double x, double y,double z) : m\_x(x), m\_y(y), m\_z(z) {}
//运算符重载
Vector3 Vector3::operator+(const Vector3 &A) const
{
Vector3 B;
B.m_x = this->m_x + A.m_x;
B.m_y = this->m_y + A.m_y;
B.m_z = this->m_z + A.m_z;
return B;
}
Vector3 Vector3::operator++()
{
this->m_x ++;
this->m_y ++;
this->m_z ++;
return \*this;
}
void Vector3::display()const
{
cout<<"(" << m_x << "," << m_y << "," << m_z << ")" << endl;
}
Vector3 operator-(const Vector3 &v1,const Vector3 &v2)
{
Vector3 B(v1.m_x - v2.m_x, v1.m_y - v2.m_y, v1.m_z - v2.m_z);
return B;
}
Vector3 operator--( Vector3 &v)
{
v.m_x--;
v.m_y--;
v.m_z --;
return v;
}
int main()
{
Vector3 v1(1, 2, 3);
Vector3 v2(2, 3, 2);
++v1;//v1.operator++(); 作为类成员函数可以显式调用
v1.display();
--v2;
v2.display();
[外链图片转存中...(img-2HgZa1jC-1715623024463)]
[外链图片转存中...(img-vXW1RYCb-1715623024463)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**