C++学习记录(持续更新)
1、vscode c++ 环境配置
-
首先下载 MingGW ,在电脑系统里的高级设置里的环境变量里的path添加史昂 MingGW 文件下的bin文件路径。
-
通过控制台 g++ - version来判断是否成功设置。
-
下载vscode 并安装相应的插件。
-
tasks.json:用于构建任务,也就是编译链接程序。
-
launch.json:用于配置调试环境,例如调试器(gdb),可执行文件路径等。这两个文件按
F5
后会自动在 .vscode 目录下生成。 -
在.vscode下配置文件launch内容, “type”: “cppvsdbg”(选择windows)和"type": “cppdbg”(选择GDB), 前者为只调试不生成后者为只运行不生成。
-
launch文件内的 program:${workspaceFolder} :表示当前文件夹路径 .
-
launch { "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", // 配置名称,将会在启动配置的下拉菜单中显示 "type": "cppdbg", // 配置类型,这里只能为cppdbg "request": "launch", // 请求配置类型,可以为launch(启动)或attach(附加) "program": "${workspaceFolder}/${fileBasenameNoExtension}.exe",// 将要进行调试的程序的路径 与task'中的exe一致 "args": [], // 程序调试时传递给程序的命令行参数,一般设为空即可 "stopAtEntry": false, // 设为true时程序将暂停在程序入口处,一般设置为false "cwd": "${workspaceFolder}", // 调试程序时的工作目录,一般为${workspaceFolder}即代码所在目录 "environment": [], "externalConsole": true, // 调试时是否显示控制台窗口,一般设置为true显示控制台 "MIMode": "gdb", "miDebuggerPath": "D:\\Environment\\MinGW\\bin\\gdb.exe", // miDebugger的路径,注意这里要与MinGw的路径对应 "preLaunchTask": "g++", // 调试会话开始前执行的任务,一般为编译程序,c++为g++, c为gcc 与task中的lable一致 "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] } tasks.json 修改你的command和cwd为你自己的路径 { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "type": "shell", "label": "g++", //这里注意一下 "command": "D:\\Environment\\MinGW\\bin\\g++.exe",//路径不可带中文 "args": [ "-g", "${file}", "-o", "${fileDirname}\\${fileBasenameNoExtension}.exe", "-ggdb3", // 生成和调试有关的信息 "-Wall", // 开启额外警告 "-static-libgcc", // 静态链接 "-std=c++17", // 使用c++17标准 "-finput-charset=UTF-8", //输入编译器文本编码 默认为UTF-8 "-fexec-charset=GB18030", //输出exe文件的编码 "-D _USE_MATH_DEFINES" ], "options": { "cwd": "D:\\Environment\\MinGW\\bin" }, "problemMatcher": [ "$gcc" ], "presentation": { "echo": true, "reveal": "always", // 在“终端”中显示编译信息的策略,可以为always,silent,never "focus": false, "panel": "shared" // 不同的文件的编译信息共享一个终端面板 }, } ] } c_cpp_properties.json { "configurations": [ { "name": "MinGW64", "intelliSenseMode": "gcc-x64", "compilerPath": "D:\\Environment\\MinGW\\bin\\g++.exe", "includePath": [ "${workspaceFolder}" ], "cppStandard": "c++17" } ], "version": 4 }
-
以上会导致生成exe文件无法更新,要重新生成才行。
-
还有一个简便方式,下载vscode插件code run
-
run code 运行时产生乱码 选择 文件 -> 首选项 -> 设置,打开VS Code设置页面,找到 Run Code configuration,勾上 Run In Terminal 选项。设置之后,代码就会在 Terminal 中运行解决乱码。
2、C++组成部分
- #includ 标准库时使用<****>在自己建的文件夹中使用“”
- 成功时返回0,错误时返回-1
- cout位于标准std名称空间中,名称空间是指取消歧义,确认为std中的cout而不是其他库中的cout
- using namespace std可以使用于main函数里面
- 指定使用std中的cout时 可以书写 using std::cout
- cout<<**<<之间可以使用函数输出数据
3、变量与常量
-
int a=10 编译器负责将a关联到内存
-
变量中的数据存储在内存中
-
子函数结束后将销毁所有局部变量,并归还他们所占的内存。
-
函数命名时每个单词的首字母大写(Pascal),变量名都小写(骆驼)
-
bool布尔变量声明
-
编译器把字符转换为可以存储到内存中的数字,如Y根据ASCII码转换为89进行存储。
-
单引号可以增加数据的可读性,如700‘000
-
sizeof的使用 sizeof(int) 输出为字节数 int为4字节
-
short **y{x}**赋值语句=》y=x但又多了一个防止x缩小的功能,如x为int 但y为short时short y{x}就会报错提醒你不可缩小。
-
可以使用auot来声明变量,系统将自动识别最符合该数的类型,但是必须初始化时赋值。
-
typedef 可以设置将int这个名词转换为自己想要的名称,如 typedef int myint这里int x和myint y是一样的都是int类型。
-
常量使用const,define在c++中被弃用不推荐因为define是预处理器宏,编译器只进行文本替换,不关心类型问题。
-
在c++11中还有constexpr常量表达式,用法如 : constexpr double GetPi(){return 22/7;}这是为使用常量提供优化的可能,如我需要2Pi只需定义常量函数GetPi2(){return 2*GetPi;}即可,编译阶段会将所有GetPi的函数直接用结果3.14…这个值来替换而不需要再次计算。
-
枚举常量适用于天气,星期等 用法为 enum week{ Mon=1,Tue,…,Sun};
#include<iostream> using namespace std; int sum(int a,int b){ return a+b; } int main() { // const double pi=3.1415926535;//const + 类型 (声明常量时) // enum week{ // Mon=1,//从1开始往下递增+1 // Tue, // Wed, // Thur, // Fri, // Sat, // Sun // }; // week today=Wed; // cout<<today<<endl;//输出数字3,若Mon没有赋值1则默认从0开始 // int x,y; std::cout<<"hello world"<<"my name is fanjingfeng" <<endl; // cout<<"x="; // cin>>x; // cout<<endl<<"y="; // cin>>y; // x=700'000; // y=5'000'000; // cout<<endl<<"sum="<<x+y<<endl; // cout<<"sum(函数)"<<sum(x,y)<<endl; // typedef int myint;//将int转换为myint这个名称 // myint x=1000; // short y{x};//防止int缩小为short的表达式 // cout<<sizeof(x)<<endl; return 0; }
4、管理数组和字符串
-
静态数组 int x[10]={0},进行了数组x的初始化**,int x[10]={3,7,8}**则前三个数为u3,7,8后面默认初始化为0。
-
多维数组映射 x[5] [5]={{},{},{},{},{}}可以这样赋值,也可以x[5] [5]={}赋值。
-
动态数组使用了vectory dymyarray(3);初始化了dymyarray长度为3的数组,当要超过长度3的数组时,可以使用dymyarray.push_back(value)的函数。
-
在使用vector(3)来当数组时遇到控制台不输出的问题,采用了devc++解决了
-
devc++初始不支持c++11,在 【工具】里找到【编译选项】 里 【编译时加入以下命令】处打钩,然后在空白栏输入【-std=c++11】即可。
-
在数组中添加\0并不会使数组长度改变,只会让字符串提前结束。同时char数组里面没有\0,cout会导致出错。
-
cin遇到空格回车会结束,而getline遇到空格不会结束,读取换行才结束。
#include<iostream> #include<vector> using namespace std; int main() { std::cout<<"hello world "<<"my name is fanjingfeng" <<endl; // // int myNum[2][3]={{1998,8,1},{2,56}};//大括号里缺一个数也会默认初始化为0 // // cout<<"hello world"<<"my name is fanjingfeng" <<endl; // // cout<<myNum[0][0]<<"-"<<myNum[1][2]<<endl; // char mystring[]={'h','e','l','l','o',' ','w','o','r','l','d' ,'\0'};//不添加\0会导致输出出错 // cout<<mystring<<sizeof(mystring)<<endl; // vector<int> dyMyArray;//有问题,无法输出 // // cout<<dyMyArray[0];// // for(int i=0;i<3;i++){ // dyMyArray[i]=i; // } // std::cout<<dyMyArray.size()<<endl;//不可用sizeof // dyMyArray.push_back(25); // std::cout<<dyMyArray.size()<<endl; // std::cout<<dyMyArray[3]; // return 0; }
5、使用表达式、语句和运算符
- cout<<hello world要换行要添加\否则会出错。
- 位运算,要包含#include,c++位运算的符号:~为取反,&为和,|为或,^为异或。使用时为bitsit<8> x=(0x0f);运算符的使用方法同普通运算符加减乘除般。
6、控制程序流程
-
if else和witch() {case xxx:;berek;}在处理switch case时务必使用枚举类型以提高代码可读性。
-
C++11ford 新方式for(int i:int x[10]{0,1,3})
-
while(-1)==while(true),while(0)==while(false)
-
for(;;i++)和for(;;++i)逻辑上无区别,但是效率上,++i占优,因为i++需要临时空间存储;
-
#include<iostream> #include<vector> #include<bitset> #include<string> using namespace std; int main() { // const int x=1 // x=x+1; // cout<<x;//报错常量不可以被改变 // static int x=1 // x=x+1; // cout<<x//此处不可被修改 // enum dayofweek{ // Mon,//从0开始往下递增+1 // Tue, // Wed, // Thur, // Fri, // Sat, // Sun//相当于直接定义了const sun为6 // }; // int day; // cout<<"输入几号 "<<endl; // cin>>day; // switch(day%7){ // case Mon:cout<<"Todya is Mon"; // break; // case Tue:cout<<"Todya is Tue"; // break; // case Wed:cout<<"Todya is Wed"; // break; // case Thur:cout<<"Todya is Thur"; // break; // case Fri:cout<<"Todya is Fri"; // break; // case Sat:cout<<"Todya is Sat"; // break; // case Sun:cout<<"Todya is Sun"; // break; // } c++11 for循环新的遍历方式 // for(int i:dayofweek){//不可遍历枚举 // cout<<"Today is "<<i; // } // string Dayofweek[]={"Mon","Tue","Wed","Thur","Fri","Sat","Sun"};//数组不说明多长也可以。 // for(string i :Dayofweek){//c++11 for循环新的遍历方式 // cout<<"Today is --"<<i<<endl; // } //循环嵌套计算斐波那契数列 char flag='N'; // vector<int> Fei(2){0,1};//无法用此方法进行初始化 vector<int> Fei(2); Fei[0]=0; Fei[1]=1; do{ for(int i=0;i<5;i++){ int x=Fei[Fei.size()-1]+Fei[Fei.size()-2]; cout<<x<<" "; Fei.push_back(x); } cout<<"是否继续计算(Y/N)"; cin>>flag; } while(flag=='Y'); cout<<"整个数组为: "<<endl; for(int i:Fei){ cout<<i<<" "; } } 7、使用函数组织代码
7、函数调用
-
void的函数不需要返回值
-
函数重载是相同函数名,但参数设置不同时,不能通过返回值类型设置重载
-
x[]={1,2,3,4,5};cout<<sizeof(x);//原本长度为5输出却为8 sizeof()函数是求数组所占的内存空间大小(不是长度)。当在函数中使用该方法求数组长度时,由于数组作为函数参数传入函数的过程中会“退化”为指针,因此将会导致sizeof(arr)求出的是一个指针的内存空间大小,而非数组的内存空间大小–>从而导致计算的长度错误
-
指针从本质上讲是一个变量,变量的值是另一个变量的地址,指针在逻辑上是独立的,它可以被改变的,包括指针变量的值(所指向的地址)和指针变量的值对应的内存中的数据(所指向地址中所存放的数据)。
-
引用从本质上讲是一个别名,是另一个变量的同义词,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化(先有这个变量,这个实物,这个实物才能有别名),而且其引用的对象在其整个生命周期中不能被改变,即自始至终只能依附于同一个变量(初始化的时候代表的是谁的别名,就一直是谁的别名,不能变)
-
微处理器处理函数调用,c++使用CALL实现,CALL实现指出下一条指令所在的位置,RET弹出。
-
内联函数,比较简单的函数,相比运行,函数调用 消耗的资源更多,避免这种情况,设置了内联函数,通过关键字inline发出请求。相当于将内联函数的内容直接放入主函数中,提高代码执行速度。编译器会自动尽可能将你的函数内联。
#include<iostream> #include<vector> #include<bitset> #include<string> using namespace std; //double caculate_circle(double radius,double pi=3.14){//此oi存在默认值,赋值可给可不给 // return 2*pi*radius; //} /* 重载 */ //double caculate_circle(double radius){ // cout<<"这是一个double参数,返回值double "; // double circle=2*radius*3.14; // return circle; //} // //double caculate_circle(double radius,double Pi){ // cout<<"这是两个double参数,返回值double "; // double circle=2*radius*Pi; // return circle; //} // //double caculate_circle(int radius){ // cout<<"这是一个int参数,返回值double "; // double circle=2*radius*3.14; // return circle; //} //int caculate_circle(int radius){//不能通过返回值类型设置重载** // cout<<"这是一个int参数,返回值int "; // int circle=2*radius*3.14; // return circle; //} /* 数组为参数 */ void print_array (int array[],int length){ // cout<<array;//输出数组首地址 // cout<<sizeof(array);//原本长度为5输出却为8,这是因为输入数组为参数时,会自动将其转化为指针,所以sizeof(array)实际上是array这指针的大小,解决方法是将长度传进来 for(int i=0;i<length;++i){ array[i]=i; cout<<array[i]<<" "; } cout<<endl; } int main() { // cout<<"默认pi时 "<<caculate_circle(15)<<endl; // cout<<"自定义pi时 "<<caculate_circle(15,3.1415926)<<endl; int out_2=caculate_circle(x); cout<<cout_2<<"输入为一个int参数,返回为int"<<endl;//不能通过返回值类型设置重载* // int x=3; // double Pi=3.14,y=3; // double out_1=caculate_circle(y); // cout<<out_1<<" 输入为一个double参数,返回为double"<<endl; // out_1=caculate_circle(x); // cout<<out_1<<"输入为一个int参数,返回为double"<<endl; // out_1=caculate_circle(y,Pi); // cout<<out_1<<"输入为两个double参数,返回为double"<<endl; //数组为参数 int x[]={1,2,3,4,5}; int length=sizeof(x)/sizeof(x[0]);//同时,sizeof不是测多长的,而是测多大的 // cout<<length<<endl; print_array(x,length); for(int i=0;i<length;++i){ cout<<x[i]<<" "; } }
8、阐述指针与引用
-
指针初始化,可以将int *mypointer=NULL;NULL为可检查的值,若不初始化,默认会进行初始化。
-
sizeof(指针)时其值与操作系统和编译器有关,与指针的类型无关。指针大小相同。
-
new与delete使用时,new将返回指向一个指针,只想分配的内存。
-
int *pia = new int[10]; // 10个未初始化int
int *pia2 = new int[10](); // 10个值初始化为0的int -
delete 数组形式为 delete[] xxx
-
//指针二维数组 e = new int*[10]; // e是指针指向一个指针数组(size 为10) for (int i = 0; i < 10 ; ++i) { e[i] = new int[10]; //e[i]则是指针数组的一个指针 这边再分配内存 每个e[i]指向的是一个一维数组的头地址 //类比c 这类方法就是动态内存分配二维数组 }
-
指针输出值只能为*(指针+偏移量)的形式输出
-
const与指针的组合,const int指针,int * const 指针,const int const 指针。
-
指针传递给函数时,可以在函数关键字处设置const来设置其值是否可变。
-
指针与数组的相似int myNum[10],其中myNum即为数组首地址myNum[0]==*myNum
-
内存分配请求是否得到满足,如果失败则引发std::bad_alloc的异常和进行try catch的异常处理方式。
-
通过采用new(nothrow),new失败则会返回NULL。 在实际开发中,内存的分配失败是非常普通的,它们通常在植入性和不支持异常的可移动的器件中发生更频繁。因此,应用程序开发者在这个环境中使用nothrow new来替代普通的new可以更加安全!
-
引用的意义,如果函数调用某变量,但该变量占用大量内存,使用int sum(int a,int b)时,a,b就会对变量进行复制,导致开销很大,而使用别名相当于直接操作变量,省去了对a,b的复制。
-
&(引用)==>用来传值,出现在变量声明语句中位于变量 左边时,表示声明的是引用.
&(取地址运算符)==>用来获取首地址,在给变量赋初值时出现在等号右边或在执行语句中作为一元运算符出现时表示取对象的地址.
-
通过const对别名进行限定,防止通过别名修改变量值。
#include<iostream>
using namespace std;
//
//double Area(const double *PI,const int * const R,double * const A){//R值指针的地址和所指的值都不可改变,S指针所指的地址不可改变,值可以改变
// //PI设置为常量,因此在引用时,防止误会必须将PI的指针指向的值也设为const
// *A=(*PI)*(*R)*(*R);
// cout<<endl<<"面积为: "<<*A;
*R=*R+1;
cout<<endl<<"对*R进行改变后 :"<<*R ;//read_only
//}
int main(){
// int x=10,y=20;
// int *mypointer=&x;//初始化赋值针时要附上地址
// int *test;//不给NULL也默认为NULL
// if(test==NULL){
// cout<<"赋值为空指针"<<endl;
// }
// cout<<*mypointer<<endl;
// mypointer=&y;//指针再次赋值
// cout<<*mypointer<<endl;
// cout<<dec<<y<<endl;//dec为以十进制输出,hex为以十六进制输出
// cout<<hex<<mypointer<<endl;
// cout<<&mypointer<<" "<<mypointer<<endl;
/*
动态分配内存(new和delete的用法)
*/
// int *nothrowpointer=new(nothrow) int ;//通过采用new(nothrow),new失败则会返回NULL
// delete nothrowpointer;
//
// int * newpointernum=new int;
// *newpointernum=10;
newpointernum++;//++指向是下一个int的地址,而不是下一个字节,如数组可查看
cout<<newpointernum;
// delete newpointernum;//删除new的空间
//
// int * newpointerarray=new int[10];//指定多少个元素分配内存
// *(newpointerarray)=15;
// *(newpointerarray+1)=20;
// cout<<*newpointerarray;
// delete []newpointerarray; //数组删除delete[] xxx
// const double PI=3.14;
// int R=0;
// double S=0;
// cout<<"输入半径R: ";
// cin>>R;
// Area(&PI,&R,&S);
// R=R+1;
// cout<<endl<<"对R进行改变后 :"<<R ;//函数里的const的作用域只限于函数中,不会传递到主函数中
//int MyNum[10]={10,15,20};
//cout<<MyNum[0]<<" 利用指针*MyNum 的值 "<<*MyNum<<endl;
//cout<<MyNum[1]<<" 利用指针*(MyNum+1)的值 "<<*(MyNum+1)<<endl;
/*
引用的使用
*/
}
9、类和对象
-
实例化对象时,也可以new对象,分配动态内存。
-
对象访问其成员有三种方法,Human firstman;时只需firstman.name通过.来访问。Human * firstman=**new Human()**时,可以通过(*firstman).name或者firstman->name
-
构造函数可以在类里面声明,也可以在类外面声明。
-
构造函数以在重载的情况下私有。
-
带默认值的构造函数可省略传参的步骤。
-
包含初始化列表的构造函数,形式为Human(int xxx):y(xxx){}
-
析构函数无实例化,直接~Human().
-
string类已包含析构函数,不建议使用char*
-
delete和new包含在类中从而对外隐藏内存管理过程
-
析构函数不可以重载
-
浅复制,当类里面的指针指向一个内存处时,有函数需要进行类传参导致两个指针指向同一处内存。从而引发函数结束,调用了析构函数对内存进行了销毁,当整个main函数结束时,类自动调用析构函数从而引发错误。简而言之就是两个指向同一处内存的指针,析构函数多次调用引发错误。
-
在 C 语言中,字符串是以空字符做为终止标记(结束符)的。所以,C 语言字符串的最后一个字符一定是 \0。请确保所有的字符串都是按照这个约定来存储的,不然程序就会因为莫名其妙的错误退出。strlen 函数返回的是字符串的实际长度(所以不包括结尾的 \0 终止符)。所以为了保证有足够的空间存储所有字符,我们需要在额外 +1。如"abc",占四个字节,strlen的值是3
-
C++规则,只可能对排列在最后的那些参数提供默认参数 。初始化列表也如此。
-
深复制添加一个复制制造类函数。复制构造函数时要使用引用,否则会无限循环。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JlJqR1Ln-1645512721037)(C:\Users\fanji\AppData\Roaming\Typora\typora-user-images\1644198006075.png)]
-
**系统会根据实参的类型决定调用普通构造函数或复制构造函数 。**如上图
-
string和智能指针类实现了复制构造函数
-
使用eplicit可以避免隐式转换,隐式转换就是当你只有一个类型T1,但是当前表达式需要类型为T2的值,如果这时候T1自动转换为了T2那么这就是隐式类型转换。
-
严格的使用复制构造函数会影响性能,因此可以使用移动构造函数。
-
不允许复制类时,可以在private中声明复制构造函数,即可禁止复制。
-
单例设置时,可以将构造函数,复制构造函数,赋值创建对象都放入私有当中,在public中建立static来设置全局唯一对象。
-
静态变量或函数式在系统编译期,main函数运行前就已经分配内存的 , 静态局部变量在程序执行到该对象声明时,会被首次初始化.其后运行到该对象的声明时,不会再次初始化 .局部静态变量 不能被其作用域之外的其他模块调用,其调用范围仅限于声明该变量的函数作用域当中 。
-
在设置president类时,为了控制其单独性,首先在private中声明构造函数,复制构造函数,赋值创建对象。在public中创建
static president& GetInstant(){ static president onlyinstant; return onlyinstant; }
-
Foo bar = Foo(); Foo& bar1 = bar; //这里bar1是bar的一个引用,就相当于bar1就是bar
-
析构函数设置为私有时在new类后,需要在public中设置函数进行delete,才可以销毁。在外部进行delete是无法进行析构的。
-
A a; // a存在栈上 A* a = new a(); // a存在堆中
以上两种方式皆可实现类的实例化,有无new的区别在于:
1 前者在栈中分配内存,后者在堆中分配内存
2 动态内存分配会使对象的可控性增强
3 大程序用new,小程序不加new,直接申请
-
this指针在类里面函数调用类里的函数编译器会隐式的传递this指针
-
友元函数关键字friend
-
共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型
union Test {
int a;
double pi;
char str[20];
};
在声明union Test x时代表x这一联合体既可以是int,也可以是double,同时也能是char[],但是同时只能有选一种。如x.a=10;
-
聚合初始化,如int x[10]={1,2,3}即聚合初始化,在类中也存在
class student{ public: string name; int Class; int age; } int main(){ student fan{"fjf",10,20};//此为类初始化的聚合 }
#include<iostream>
#include<string>
#include<string.h>
using namespace std;
class Human{
public:
Human(){
}
// Human(string myname,string mydateofbirth,string myplaceofbirth,string mygender="男"){
// name=myname;
// dateofbirth=mydateofbirth;
// placeofbirth=myplaceofbirth;
// gender=mygender;
// }
explicit Human(char *hello,string myname,string mydateofbirth,string myplaceofbirth,string mygender="男")://初始化列表只能在后面的参数 ,explicit防止进行隐式转换
name(myname),dateofbirth(mydateofbirth),placeofbirth(myplaceofbirth),gender(mygender){//包含初始化列表的构造函数
SayHello=new char[strlen(hello)+1];//字符串是以空字符做为终止标记(结束符)的。
//所以,C 语言字符串的最后一个字符一定是 \0。请确保所有的字符串都是按照这个约定来存储的,不然程序就会因为莫名其妙的错误退出。
//strlen 函数返回的是字符串的实际长度(所以不包括结尾的 \0 终止符)。所以为了保证有足够的空间存储所有字符,我们需要在额外 +1。如"abc",占四个字节,strlen的值是3
strcpy(SayHello,hello);
// cout<<" -- "<<SayHello<<endl;
}
// Human(const Human &CopySource){
// SayHello=NULL;
// if(CopySource.SayHello!=NULL){
// SayHello=new char[strlen((CopySource.SayHello)+1)];
// strcpy(SayHello,(CopySource.SayHello));
// cout<<endl<<"...正在使用复制构造函数..."<<endl;
// }
// }
void SayHelloHuman(){
cout<<" -- "<<SayHello<<endl;
}
~Human(){
delete [] SayHello;
}
private: //不设public默认为private类型
// Human();//私有报错
friend void VisitHuman(const Human &myfriend);
Human(const Human &CopySource){
SayHello=NULL;
if(CopySource.SayHello!=NULL){
SayHello=new char[strlen((CopySource.SayHello)+1)];
strcpy(SayHello,(CopySource.SayHello));
cout<<endl<<"...正在使用复制构造函数..."<<endl;
}
} //复制构造函数放在私有里即可防止被复制
string name;
string dateofbirth;
string placeofbirth;
string gender;
char * SayHello;
public:
void SetName(string humanname){
this->name=humanname;//编译器会隐藏this指针 ,得用->而不是.
}
void SetDateOfBirth(string humandateofbirth){
dateofbirth=humandateofbirth;
}
void SetPlaceOfBirth(string humanplaceofbirth){
placeofbirth=humanplaceofbirth;
}
void Setgender(string humangender){
gender=humangender;
}
void Talk(string texttotalk);
void IntroduceSelf(){
cout<<"我是"<<name<<" "<<"性别"<<gender<<" "<<"出生于"<<placeofbirth<<" "<<"日期为"<<dateofbirth;
}
};
void SayHelloHuman_out(Human firstman){
cout<<endl<<" --- "<<"启动函数SayHelloHuman_out"<<endl;
firstman.SayHelloHuman();
firstman.~Human();
return;
}
void VisitHuman(const Human &myfriend)//在类里面声明友元,友元函数即可访问类里private的内容
{
cout<<endl<<"友元函数 名字输出为: "<<myfriend.name<<endl;
}
int main(){
// Human firstman;
// firstman.SetName("张三");
// firstman.Setgender("男");
// firstman.SetDateOfBirth("1999.08.01");
// firstman.SetPlaceOfBirth("杭州");
// firstman.name="张三";
// firstman.dateofbirth="1999.08.01";
// firstman.placeofbirth="杭州";
// firstman.gender="男";
// cout<<"姓名: "<<firstman.name<<endl<<"出生日期: "<<firstman.dateofbirth<<endl<<"出生地: "<<firstman.placeofbirth<<endl;//当属性成员为public时可这样访问当private时则不行。通常通过函数来传递属性成员
Human firstman("你好","张三","1998.08.01","杭州");//不添加firstman也可以运行,只是没有实例化对象
firstman.IntroduceSelf();
// cout<<endl<<"启动复制构造函数"<<endl;
// SayHelloHuman_out(firstman);
VisitHuman(firstman);
firstman.~Human();//按理来说浅复制多次析构会报错,但devc++5.11不显示问题 ,可能自动生成了拷贝构造函数
// cout<<endl<<"析构执行";
}
10、实现继承
-
基类属性设置为protected后,其属性可以在继承类里操作,但不能在主函数里直接拿出来操作。
-
子类继承父类会继承除父类构造函数的所有,因此子类要调用父类构造函数实现对父类成员的初始化。
-
覆盖是存在类中,子类覆盖是从基类继承过来的方法(函数)。但是函数名、返回值、参数列表都必须和基类的方法相同。 被覆盖的方法不能为private和final。在private子类覆盖中只是新定义了一个方法,并没有对其进行覆盖 。重载要参数不一致。
-
子类覆盖会全部隐藏父类该函数的所有重载。即使父类函数有参数,子类函数无参数,在调用时会发生子类函数无法添加形参。
-
基类在子类之前实例化。构造函数是先基类再子类。
-
析构函数与构造函数相反,先子类再基类。
-
私有继承意味着派生类中基类所有的方法和成员都是私有的,无法外部调用。main中不同继承情况外部调用如下:
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WBZpWcp0-1645512721038)(C:\Users\fanji\AppData\Roaming\Typora\typora-user-images\1644286473767.png)]
-
不管是哪种继承方式,派生类中新增成员可以访问基类的公有成员和保护成员,无法访问私有成员 。 而继承方式影响的是派生类继承成员访问属性 。
-
通常private和protected继承有必要时才使用,更多的是将要继承的类作为派生类的一个成员,避免发生兼容瓶颈。
-
切除问题,子类赋值到基类,则只会将子类中基类的部分赋值过去,从而造成裁剪数据。
-
final禁止继承,因此final不能用于基类。
-
在选择继承时,不要因为微不足道的方法二创建继承层次结构,而是可以直接在类里面直接重写该方法。
-
#include<iostream> #include<string> using namespace std; class fish {//基类不可以使用final private: int age; protected://protected下的属性只能该类和继承该类的类进行访问,在主函数main'中就不可以进行访问 bool IsFreshWaterFish; public: fish(bool getIsFreshWaterFish){ IsFreshWaterFish=getIsFreshWaterFish; cout<<"启动了fish无参构造函数 "; } fish(){ cout<<"启动了fish有参构造函数 "; } void swim(){ cout<<"鱼在水里游"<<endl; } void swim(string place){//重载 cout<<"鱼在"<<place<<"里游"<<endl; } bool GeteWaterFishTpye()//bool和string不可以转换 { return IsFreshWaterFish; } ~fish(){ cout<<"执行fish的析构"<<endl; } }; class Tuna final:public fish{//final禁止继承 public: Tuna():fish(false){ // IsFreshWaterFish=false; cout<<"启动了Tuna构造函数"<<endl; } void swim(){ cout<<"子类类调用基类函数 "; fish::swim(); cout<<"鱼在海里游"<<endl; //覆盖了fish // cout<<"鱼的年龄为 "<<age<<endl;//age为私有成员 } ~Tuna(){ cout<<"执行Tuma的析构"<<endl; } }; class Carp:public fish{ public : Carp(){ cout<<"启动了Carp构造函数"<<endl; } Carp(bool x):fish(x){//先启动fish'的构造函数,再启动carp的构造函数 .父类构造函数赋值只有类似初始化列表的方式 // IsFreshWaterFish=true;//直接使用,不需要fish.IsFreshWaterFish cout<<"启动了Carp构造函数"<<endl; } void swim(){ cout<<"鱼在河里游"<<endl; } ~Carp(){ cout<<"执行Carp的析构"<<endl; } }; int main(){ fish myfish; Tuna TunaFish; Carp CarpFish; // cout<<"金枪鱼是淡水鱼吗? "<<TunaFish.GeteWaterFishTpye()<<endl; // cout<<"鲤鱼是淡水鱼吗?"<<CarpFish.GeteWaterFishTpye()<<endl; // TunaFish.swim(); // TunaFish.swim("长江");//父类中所有swim重载都被覆盖,导致无法执行 TunaFish.fish::swim();//调用基类中swim的方法 TunaFish.fish::swim("太平洋"); myfish=TunaFish;//执行fish中的swim,鱼在水里游,而不是在海里游 cout<<"-------"; myfish.swim(); // Tuna AnotherTunaFish=myfish;//基类无法给子类赋值 }
11、多态
-
当fish声明为虚函数时用,子类赋值到基类时( 类型必须是父子类关系的指针或引用 ),会表现出子类的特征,而不是基类的特征。
-
在执行函数时,若形参为类的引用,则可以通过&Fish来同时满足Tuna和Carp,这就是将派生类对象视为基类对象,并执行派生类里进行覆盖的函数,这就是多态。
-
在利用基类指针使用派生类时,delete基类会导致只进行基类的析构,不执行派生类的析构,从而导致内存泄露等问。解决方案为,对基类的析构函数进行虚函数化。
-
虚函数工作原理,每一个类当声明虚函数时会生成一个虚函数表。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-75bR6Rws-1645512721039)(C:\Users\fanji\AppData\Roaming\Typora\typora-user-images\1644386125114.png)]
-
不能实例化的基类被称为抽象类。
-
虚函数定义方式 virtual void xxx()=0;
-
虽然纯虚函数不能实例化,但是仍可将指针和引用的类型定义为抽象基类。
-
虚基类是用来在多继承中,如果父类继承自同一个父类,就只实例化一个父类 。
#include<iostream>
using namespace std;
class Animal{
public:
Animal(){
cout<<"Animal构造函数"<<endl;
}
int age;
};
class Mammal:virtual public Animal{//虚基类再多继承中使得Animal只需要实例化一个即可
};
class Bird:virtual public Animal{
};
class Reptile:virtual public Animal{
};
class Platypus:public Mammal,public Bird,public Reptile{
public:
Platypus(){
cout<<"Platypus构造函数"<<endl;
}
};
int main(){
Platypus duckBilledP;
return 0;
-
1、int GetY() const;
2、const int * GetPosition();
对于1 该函数为只读函数,不允许修改其中的数据成员的值。
对于2 修饰的是返回值,表示返回的是指针所指向值是常量
-
override放在函数后可以用来判断是否对基类的函数进行了覆盖。
-
c++无法将复制构造函数进行声明为虚函数
-
派生类进行覆盖时,务必将基类声明为虚函数,在覆盖时务必使用override
-
#include<iostream> #include<string> using namespace std; class Fish { public: virtual void PlaceOfBirth()=0;//设置纯虚函数 Fish(){ cout<<"执行Fish的构造函数"<<endl; } virtual void swim(){ cout<<"鱼在水里游"<<endl; } virtual~Fish(){ cout<<"执行Fish的析构函数"<<endl; } }; class Tuna :public Fish{ public: Tuna(){ cout<<"执行Tuna的构造函数"<<endl; } void swim(){ cout<<"鱼在海里游"<<endl; } void PlaceOfBirth(){//重新定义虚函数 cout<<"生于太平洋"<<endl; } ~Tuna(){ cout<<"执行Tuna的析构函数"<<endl; } }; class Carp:public Fish{ public : Carp(){ cout<<"执行Carp的构造函数"<<endl; } void PlaceOfBirth(){ cout<<"生于长江"<<endl; } // void swim() const override{//添加了const后,特征值不一致,导致没有覆盖Fish中的swim // cout<<"鱼在河里游"<<endl; // } void swim() override final{ //override 使用来判断是否进行了覆盖 //添加final可以禁止在Carp下再继承,重新声明swim cout<<"鱼在河里游"<<endl; } ~Carp(){ cout<<"执行Carp析构函数"<<endl; } }; void GetPlaceSwim(Fish &MyFish){//当Fish为纯虚函数,仍旧可以将Fish的别名作为形参 //通过设置形参为基类的引用,从而达到Tuna'和Carp都可以放入,并运行派生类的函数。 MyFish.swim();//引用无法delete // delete MyFish; } void GetPlaceSwim(Fish *MyFish){ MyFish->swim(); delete MyFish; } int main(){ // Fish MyFish;//未设置纯虚函数前可以实例化,设置后便不能实例化 // MyFish.swim();//未设置纯虚函数前,虚函数可以直接调用 // Tuna MyTuna;//未将纯虚函数重新声明 // MyTuna.PlaceOfBirth(); // Carp MyCarp; // // MyFish=MyTuna; // MyFish.swim(); //未覆盖swim // Fish *MyFish=new Tuna; //先构造FIsh在构造Tuna // MyFish->swim(); //用指针可以覆盖 // delete MyFish; //只进行Fish的析构 // Tuna *MyTuna=new Tuna; // GetPlaceSwim(MyTuna);//同上,先FIsh,Carp构造只有Fish析构。 // //不调用派生类的析构函数可能会导致资源未释放、内存泄露等问题 为解决这类问题,可将析构函数声明为虚函数 // // GetPlaceSwim(MyTuna);//多态 // GetPlaceSwim(MyCarp); Carp MyCarp; MyCarp.swim(); }
12、运算符类型与运算符重载
- 前后缀自加(自减)不同之处:
Date &operate ++()前缀加
Date operate ++(int)后缀加
-
operator const char*(){//未理解,暂记 此为cout重载 ostringstream formattedDate; formattedDate<<month<<"/"<<day<<"/"<<year; dateInstring = formattedDate.str(); return dateInstring.c_str(); // return formattedDate.str();//局部变量被销毁,导致返回为空 // string date=""+std::to_string(month)+"/"+std::to_string(day)+"/"+std::to_string(year); // return date; }
-
双目运算符重载,+、-返回类型为Date,+=、-=返回类型为void,>,<,<=,>=,==,!=返回类型为bool,=运算需要进行深复制。
-
下标运算符,在设置mystring时,const char& operator[](index){}
-
C++函数在return一个局部对象的时候,会调用复制构造构造,生成一个临时对象。
-
函数运算符operator(),void operator() (string input )const1`
-
&&a 其中&&为右值,左值为含有存储空间的,右值通常为存在寄存器当中的。
-
在什么情况下系统会调用拷贝构造函数:(三种情况)
(1)用类的一个对象去初始化另一个对象时 如c2 (cl);
(2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用
(3)当函数的返回值是类的对象或引用时
-
一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。 例如,调用date.SetMonth(9) <===> SetMonth(&date, 9),this帮助完成了这一转换 .
-
为什么赋值运算符要返回引用??
因为赋值操作会改变左值,而 + 之类的运算符不会改变操作数,所以说赋值运算符重载要返回引用以用于类似 (a=b)=c 这样的再次对a=b进行写操作的表达式。+ 返回一个临时对象是合情合理的 ,你若返回引用大多数情况下也不会出错或导致某个操作数被意外修改,但这就使(a+b)=c这样的表达式可以出现,这就有点不符合约定了,当然,你也可以让 + 返回一个常引用
-
#include <iostream> #include<string> #include<string.h> using namespace std; class String { private: char *str; int len; public: String(){ cout<<"构造函数"<<endl; } String(const char* s);//构造函数声明 String operator=(const String& another);//运算符重载,此时返回的是对象 void operator ()(){ cout<<str<<endl; } void show() { cout << "value = " << str << endl; } /*copy construct*/ String(const String& other) { len = other.len; str = new char[len + 1]; strcpy(str, other.str); cout << "copy construct" << endl; } ~String() { delete[] str; cout << "deconstruct" << endl; } }; String::String(const char* s)//构造函数定义 { cout<<"构造函数"<<endl; len = strlen(s); str = new char[len + 1]; strcpy(str, s); } String String::operator=(const String &other)//运算符重载 { if (this == &other) return *this; // return; delete[] str; len = other.len; str = new char[len + 1]; strcpy(str, other.str); return *this; // return; } //String getreturn(){ // return String();//不启动复制构构造函数 //} String getreturn(){ String x("Hello"); return x; } int main() { String str1("abc"); // str1(); // str1.show(); String str2("123"); String str3("456"); str1.show(); str2.show(); str3.show(); str3 = str1 = str2;//str3.operator=(str1.operator=(str2)) str3.show(); str1.show(); String strx=getreturn();//只调用了一次构造函数 return 0; }
1];
strcpy(str, other.str);
cout << “copy construct” << endl;
}
~String()
{
delete[] str;
cout << "deconstruct" << endl;
}
};
String::String(const char* s)//构造函数定义
{
cout<<“构造函数”<<endl;
len = strlen(s);
str = new char[len + 1];
strcpy(str, s);
}
String String::operator=(const String &other)//运算符重载
{
if (this == &other)
return *this;
// return;
delete[] str;
len = other.len;
str = new char[len + 1];
strcpy(str, other.str);
return *this;
// return;
}
//String getreturn(){
// return String();//不启动复制构构造函数
//}
String getreturn(){
String x(“Hello”);
return x;
}
int main()
{
String str1(“abc”);
// str1();
// str1.show();
String str2(“123”);
String str3(“456”);
str1.show();
str2.show();
str3.show();
str3 = str1 = str2;//str3.operator=(str1.operator=(str2))
str3.show();
str1.show();
String strx=getreturn();//只调用了一次构造函数
return 0;
}