C++
本帖最后由 bin554385863 于 2019-8-3 17:42 编辑2019年8月1日20:55:53
2019年8月3日17:43:55 修改类的定义
类的定义
class mycomplex
{
private:
double re, im;//类的数据
public:
mycomplex(double r = 0, double i = 0)
: re(r), im(i)
{
;
}
double real() const//成员函数
{
return re;
}
double imag() const//成员函数
{
return im;
}
};
关键字:vector
快速动态数组,可以再运行阶段扩充数组
可以插入,删除,索引数组数据
定义:
vector<date type> datename;
vector<date type> datename(size, date)
向datename中写入size个date
本帖最后由 bin554385863 于 2019-8-14 00:12 编辑
2019年8月8日00:52:30
函数指针的用法
#include <iostream>
using namespace std;
/*加法 */
int add(int arg1, int arg2)
{
return arg1 + arg2;
}
/*乘法 */
double mult(double arg1, double arg2)
{
return arg1 * arg2;
}
/*减法 */
int subt(int arg1, int arg2)
{
return arg1 - arg2;
}
/*除法 */
double divi(double arg1, double arg2)
{
return arg1 / arg2;
}
void calc(int arg1, int arg2)
{
double res = 0;
int (*pcalc)(int, int);
double (*pcalc_c)(double, double);
char op;
cout << "请输入运算式" << endl;
cin >> arg1>>op>>arg2;
switch (op)
{
case '+':
pcalc = add;
res = pcalc(arg1, arg2);
break;
case '-':
pcalc = subt;
res = pcalc(arg1, arg2);
break;
case '*':
pcalc_c = mult;
res = pcalc_c(arg1, arg2);
break;
case '/':
pcalc_c = divi;
res = pcalc_c(arg1, arg2);
break;
default:
break;
}
cout << res << endl;
}
int main(int argc, char const *argv[])
{
int b = 0, c = 0;
calc(b , c);
return 0;
}
-----------------------------------------------------------------------------------------------------------------------------------------------------
Windows PowerShell
版权所有 (C) Microsoft Corporation。保留所有权利。
PS E:\Users\86184\Documents\Code> & 'c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-vxyzpdfk.c0n' '--stdout=Microsoft-MIEngine-Out-nlc2nijy.y3z' '--stderr=Microsoft-MIEngine-Error-exg2ivwk.zjt' '--pid=Microsoft-MIEngine-Pid-phhor1q0.0vm' '--dbgExe=E:\My Program\MinGW\bin\gdb.exe' '--interpreter=mi'
请输入运算式
3 +9
12
PS E:\Users\86184\Documents\Code> & 'c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-3hgfugpp.4lk' '--stdout=Microsoft-MIEngine-Out-mdgp1rln.unc' '--stderr=Microsoft-MIEngine-Error-cwc0ujp0.sck' '--pid=Microsoft-MIEngine-Pid-vnhjpifc.de1' '--dbgExe=E:\My Program\MinGW\bin\gdb.exe' '--interpreter=mi'
请输入运算式
32*96
3072
PS E:\Users\86184\Documents\Code> & 'c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-0aqcrvwu.njk' '--stdout=Microsoft-MIEngine-Out-xamzervn.uy0' '--stderr=Microsoft-MIEngine-Error-sx33tg4t.0su' '--pid=Microsoft-MIEngine-Pid-x10t3xb4.5ex' '--dbgExe=E:\My Program\MinGW\bin\gdb.exe' '--interpreter=mi'
请输入运算式
56-98
-42[/color
============================================================================================
#include <iostream>
using namespace std;
/*加法 */
double add(double arg1, double arg2)
{
return arg1 + arg2;
}
/*乘法 */
double mult(double arg1, double arg2)
{
return arg1 * arg2;
}
/*减法 */
double subt(double arg1, double arg2)
{
return arg1 - arg2;
}
/*除法 */
double divi(double arg1, double arg2)
{
return arg1 / arg2;
}
void calc(double arg1, double arg2)
{
while (1)
{
double res = 0;
double (*pcalc)(double, double);
char op;
cout << "请输入运算式" << endl;
cin >> arg1 >> op >> arg2;
switch (op)
{
case '+':
pcalc = add;
res = pcalc(arg1, arg2);
break;
case '-':
pcalc = subt;
res = pcalc(arg1, arg2);
break;
case '*':
pcalc = mult;
res = pcalc(arg1, arg2);
break;
case '/':
pcalc = divi;
res = pcalc(arg1, arg2);
break;
default:
break;
}
cout << arg1 << op << arg2 << '=' << res << endl;
}
}
int main(char argc, char const *argv[])
{
int b = 0, c = 0;
calc(b, c);
return 0;
}
----------------------------------------------------------------------------------------------------------------------
PS E:\Users\86184\Documents\Code> & 'c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe' '--stdin=Microsoft-MIEngine-In-alch3axa.k5g' '--stdout=Microsoft-MIEngine-Out-20tna1oi.b2f' '--stderr=Microsoft-MIEngine-Error-gxxl5wak.ars' '--pid=Microsoft-MIEngine-Pid-bf3fmh1m.cx1' '--dbgExe=E:\My Program\MinGW\bin\gdb.exe' '--interpreter=mi'
请输入运算式
23+63
23+63=86
请输入运算式
63*98
63*98=6174
请输入运算式
63/54
63/54=1.16667
请输入运算式
87-96
87-96=-9
请输入运算式
本帖最后由 bin554385863 于 2019-8-11 12:02 编辑
2019年8月11日11:23:22
引用&指针
#include <iostream>
#include <cstdio>
int main(int argc, char const *argv[])
{
char str = 'p';
/*引用 */
char &Refstr = str;
std::cout << "初始值 " << '\n'
<< "str "
<< "= " << str << std::endl;
Refstr = 'f';
std::cout << "引用改值 " << '\n'
<< "str "
<< "= " << str << std::endl;
/*指针 */
char *pstr = &str;
*pstr = 'o';
std::cout << "指针改值 " << '\n'
<< "str "
<< "= " << str << std::endl;
std::cout << "指针本身的值" << '\n'
<< "pstr "
<< "= " << std::hex << (void *)pstr;
//printf("%#X", pstr);
return 0;
}
-----------------------------------------------------------------------------------------
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-5edmn1ex.z4c --stdout=Microsoft-MIEngine-Out-dd5gwwiv.yrl --stderr=Microsoft-MIEngine-Error-auibg2v3.efm --pid=Microsoft-MIEngine-Pid-x2jmfct3.4uu "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
初始值
str = p
引用改值
str = f
指针改值
str = o
指针本身的值
pstr = 0x61ff17
E:\Users\86184\Documents\Code>
引用不是对象因此不能对它取地址,他只是拥有指针功能的,变量的一个别名,就像人的外号,而指针则相当于身份证号 本帖最后由 bin554385863 于 2019-8-14 00:13 编辑
2019年8月12日19:35:37
C++11 auto和decltype关键字
#include <iostream>
#include <typeinfo>
using namespace std;
int main(int argc, char const *argv[])
{
float const b = 100u;//b是常量不能改变它的值
auto a = b;//auto通过数据的初始值来判断变量的类型。auto忽略了b的const声明
a = 0;//变量a不是常量,可以改变它的值
decltype(b) c = 0;//decltype返回操作数的类型,并使用它定义别的变量.c是常量它的值无法改变。decltype不会忽略const关键字。
decltype(a) e;//e不是常量
cout<<typeid(c).name()<<endl;
/*它俩的区别是:auto会忽略左值的const声明。decltype()则不会*/
return 0;
}
--------------------------------------------------------------------------------------Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-wmpqbpum.apa --stdout=Microsoft-MIEngine-Out-iq32vdue.mej --stderr=Microsoft-MIEngine-Error-veqrbw3z.oya --pid=Microsoft-MIEngine-Pid-jufr4wdn.vvm "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
f
E:\Users\86184\Documents\Code>
==================================================================
因为是mingGW所以输出的是类型缩写,用VS则会输出全称。
decltype((a)):双层括号返回的是引用类型,相当于
int &b = a;
decltype(b);
要想用auto定义一个常量必须加const关键字,
const auto c = 0;
==============================================
C++11范围for循环
#include <iostream>
/*范围for循环*/
int main(int argc, char const *argv[])
{
using std::cout;
using std::endl;
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for (int num : arr) //for(存储数组元素的临时变量 :数组指针)
{
cout << num << ' ';
}
return 0;
}
-----------------------------------------------------------------------------
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-lacyeeus.y5c --stdout=Microsoft-MIEngine-Out-fukloctk.1nv --stderr=Microsoft-MIEngine-Error-j2zzod5r.wrd --pid=Microsoft-MIEngine-Pid-g3nrssx2.sku "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
0 1 2 3 4 5 6 7 8 9
E:\Users\86184\Documents\Code>
本帖最后由 bin554385863 于 2019-8-14 00:13 编辑
2019年8月13日22:43:21
#include <iostream>
#include <string>
#include <cctype>
int main(int argc, char const *argv[])
{
using namespace std;
string str1 = "hello";
string str2 = "world";
string strc, strg;
/*getling可以读取空格,以回车结束*/
getline(cin, strg);
/*cin遇到空白字符就结束读取*/
cin >> strc;
cout<<endl;
cout << "str1 = " << str1 << endl;
cout << "str2 = " << str2 << endl;
cout << "strc = " << strc << endl;
cout << "strg = " << strg << endl;
/*string类型的字符串可以用加号拼接,用关系运算符进行比较*/
str1 = str1 + " " + str2 + " " + strg + " " + strc;
cout << "str1 = " << str1 << endl;
decltype(str1.size()) count = 0;
/*范围for循环*/
for (char c : str1)
{
/*ispunct()判断字符是不是符号*/
if (ispunct(c))
{
count++;
}
/*toupper()将小写字符转换成大写字符*/
cout << (char)toupper(c);
}
cout << "总共有" << count << "个符号";
return 0;
}
------------------------------------------------------------------------------------------------
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-br3thiie.l4p --stdout=Microsoft-MIEngine-Out-k0qugrwq.5k1 --stderr=Microsoft-MIEngine-Error-cbjsa24n.1s2 --pid=Microsoft-MIEngine-Pid-zsskfzrp.cbo "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
c language
c++!!!
str1 = hello
str2 = world
strc = c++!!!
strg = c language
str1 = hello world c language c++!!!
HELLO WORLD C LANGUAGE C++!!!
总共有5个符号
E:\Users\86184\Documents\Code>
=================================================================================
本帖最后由 bin554385863 于 2019-8-15 12:16 编辑
2019年8月14日19:07:56
向量vector的初始化
#include <iostream>
#include <vector>
#include <string>
int main(int argc, char const *argv[])
{
using namespace std;
/*列表初始化*/
vector<string> str = {"hello world"};
/*构造初始化*/
vector<string> str1(10, "A");
/*读取输入初始化*/
vector<string> str2;
string cintxt;
while (getline(cin, cintxt))
{
str2.push_back(cintxt);
if (getchar() == '\n')
{
break;
}
}
cout << "列表初始化: "
<< "str = ";
for (auto strc : str)
{
cout << strc;
}
cout << endl;
cout << "构造初始化: "
<< "str1 = ";
for (auto str1c : str1)
{
cout << str1c;
}
cout << endl;
cout << "读取输入初始化: "
<< "str2 = ";
for (auto str2c : str2)
{
cout << str2c;
}
cout << endl;
return 0;
}
*****************************************************************************
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-cz0l4q3a.zml --stdout=Microsoft-MIEngine-Out-vxgsh2vk.r4p --stderr=Microsoft-MIEngine-Error-nuzcpwcp.1nd --pid=Microsoft-MIEngine-Pid-2hpomys3.ecs "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
****** ********* ********** **********
列表初始化: str = hello world
构造初始化: str1 = AAAAAAAAAA
读取输入初始化: str2 = ****** ********* ********** **********
E:\Users\86184\Documents\Code>
===========================================================================
练习
#include <iostream>
#include <cctype>
#include <vector>
#include <string>
/*输入一组字符串,转换成大写字符后,按横输出*/
int main(int argc, char const *argv[])
{
using namespace std;
/*临时变量*/
string word;
/*向量str*/
vector<string> str;
cout << "请输入字符串" << endl;
/*临时变量接受输入*/
getline(cin, word);
/*将临时变量的数据加入向量str*/
str.push_back(word);
/*加入换行符并转换大小写*/
cout<<endl;
cout<<"输出结果"<<endl;
for (string c : str)
{
for (char b : c)
{
if (b == ' ')
{
b = '\n';
}
else
{
b = (char)toupper(b);
}
cout<<b;
}
}
return 0;
}
***************************************************************************
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-4s4jmhdg.mpa --stdout=Microsoft-MIEngine-Out-qizf4ybs.qzw --stderr=Microsoft-MIEngine-Error-yfusqgvx.q0o --pid=Microsoft-MIEngine-Pid-fshxcw4u.gg2 "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
请输入字符串
hello c++ language you are crazy
输出结果
HELLO
C++
LANGUAGE
YOU
ARE
CRAZY
E:\Users\86184\Documents\Code>
=============================================================================================
练习
#include <iostream>
#include <vector>
#include <cctype>
/*定义一个vector对象,读取一组数字,计算相邻元素的和,和头尾相加的和*/
int main(int argc, char const *argv[])
{
using namespace std;
int a = 1;
vector<int> veca;
/*输入数据*/
cout << "请输入数据" << endl;
while (a)
{
cin >> a;
/*舍去结束符0*/
if (!a)
{
break;
}
veca.push_back(a);
}
/*查看向量的元素*/
cout << "对象元素" << endl;
for (auto a : veca)
{
cout << a << "";
}
cout << endl;
decltype(veca.size()) i = 0, s = veca.size();
/*计算相邻两个元素的和*/
cout << "相邻元素的和" << endl;
for (; i < s - 1; i++)
{
cout << veca << " + " << veca << " = " << veca + veca << " ";
}
cout << endl;
/*计算头尾相加的和*/
cout << "头尾相加的和" << endl;
for (i = 0; i < (s + 1) / 2; i++)
{
cout << veca << " + " << veca << " = " << veca + veca << " ";
}
return 0;
}
***********************************************************************************************
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-t40m2dyf.5po --stdout=Microsoft-MIEngine-Out-kosvuagf.pqm --stderr=Microsoft-MIEngine-Error-ioyqbdlx.1lg --pid=Microsoft-MIEngine-Pid-hsh32dli.eyz "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
请输入数据
1 2 3 4 5 6 7 8 9 0
对象元素
123456789
相邻元素的和
1 + 2 = 3 2 + 3 = 5 3 + 4 = 7 4 + 5 = 9 5 + 6 = 11 6 + 7 = 13 7 + 8 = 15 8 + 9 = 17
头尾相加的和
1 + 9 = 10 2 + 8 = 10 3 + 7 = 10 4 + 6 = 10 5 + 5 = 10
E:\Users\86184\Documents\Code>
---------------------------------------------------------------------------------------------------------------------------------------------------
修正
#include <iostream>
#include <vector>
#include <cctype>
/*定义一个vector对象,读取一组数字,计算相邻元素的和,和头尾相加的和*/
int main(int argc, char const *argv[])
{
using namespace std;
int a = 1;
bool flag = true;
vector<int> veca;
/*输入数据*/
cout << "请输入数据" << endl;
while (true)
{
cin >> a;
/*舍去结束符0*/
if (!a)
{
break;
}
veca.push_back(a);
}
decltype(veca.size()) i = 0, s = veca.size();
/*判断是否非空集合*/
if (veca.empty() || (s < 2))
{
cout << "请输入两个以上的非零数字";
return 0;
}
/*查看向量的元素*/
cout << "对象元素" << endl;
for (auto a : veca)
{
cout << a << "";
}
cout << endl;
/*计算相邻两个元素的和*/
cout << "相邻元素的和" << endl;
for (; i < s - 1; i++)
{
cout << veca << " + " << veca << " = " << veca + veca << " ";
}
cout << endl;
/*计算头尾相加的和*/
cout << "头尾相加的和" << endl;
for (i = 0; i < (s + 1) / 2; i++)
{
cout << veca << " + " << veca << " = " << veca + veca << " ";
}
return 0;
}
***********************************************************************************
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-l1pit2dt.gjr --stdout=Microsoft-MIEngine-Out-k13clai2.sus --stderr=Microsoft-MIEngine-Error-0bil35nt.k1b --pid=Microsoft-MIEngine-Pid-344d1as5.uzi "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
请输入数据
a
请输入两个以上的非零数字
E:\Users\86184\Documents\Code>cmd /C "c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-pa1puxjz.vje --stdout=Microsoft-MIEngine-Out-hpyztzpx.p5p --stderr=Microsoft-MIEngine-Error-szvzjs0p.22y --pid=Microsoft-MIEngine-Pid-hh00x2dr.w1z "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi "
请输入数据
1 2 3 30 8545 878 a
对象元素
123308545878
相邻元素的和
1 + 2 = 3 2 + 3 = 5 3 + 30 = 33 30 + 8545 = 8575 8545 + 878 = 9423
头尾相加的和
1 + 878 = 879 2 + 8545 = 8547 3 + 30 = 33
E:\Users\86184\Documents\Code>
2019年8月15日17:35:00
迭代器
本帖最后由 bin554385863 于 2019-8-17 21:23 编辑
2019年8月17日12:10:30
#include <iostream>
#include <vector>
#include <cctype>
#include <cmath>
/*判断字符串是不是一个整数*/
bool isnumber(std::string &strargs)
{
bool flag = true;
for (char cargs : strargs)
{
if (!isdigit(cargs))
{
flag = false;
break;
}
}
return flag;
}
/*将数字字符串转换成整数*/
std::vector<int> tonumber(std::string &strargs)
{
std::vector<int> vecargs;
const int size = strargs.size();
double result = 0;
for (size_t i = 0; i < size; i++)
{
result += (strargs - 48) * pow(10, size - 1 - i);
}
vecargs.push_back(result);
result = 0;
return vecargs;
}
*****************************************************************************
#include <iostream>
#include <vector>
#include <cctype>
#include <cmath>
#include "E:\Users\86184\Documents\Code\Study\0_0_0_MyC++func.cpp"
/*使用迭代器使输入的数字翻倍输出*/
int main(int argc, char const * argv[])
{
using namespace std;
double ynum = 0;
string wnum;
vector<int> intnum;
while (true)
{
cout << "请输入数字" << endl;
cin >> wnum;
if (!isnumber(wnum))
{
cout << "输入含有其他字符,请重新输入" << endl;
continue;
}
intnum = tonumber(wnum);
vector<int>::iterator it = intnum.begin();
for (; it != intnum.end(); ++it)
{
*it = *it * 2;
}
for (int num : intnum)
{
cout << wnum << " X 2 = " << num << "";
}
cout<<endl;
}
return 0;
}
===================================================================
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-0mcjprfn.ln5 --stdout=Microsoft-MIEngine-Out-aiq00ynt.j4f --stderr=Microsoft-MIEngine-Error-gsbuvl41.e2z --pid=Microsoft-MIEngine-Pid-e3rxfvlh.qsi "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
请输入数字
q
输入含有其他字符,请重新输入
请输入数字
+
输入含有其他字符,请重新输入
请输入数字
123 1000 3
123 X 2 = 246
请输入数字
1000 X 2 = 2000
请输入数字
3 X 2 = 6
请输入数字
E:\Users\86184\Documents\Code>
==================================================================
数组和向量
#include <iostream>
#include <string>
#include <vector>
/*使用数组初始化集合*/
int main(int argc, char const *argv[])
{
using namespace std;
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 02, 35, 3, 54};
/*使用数字初始化集合*/
vector<int> vec(begin(arr), end(arr));//使用begin()和end()函数,数组名作为参数,获取数组的头指针和尾部指针
vector<int> vecx(begin(arr) + 5, begin(arr) + 10);
for (int i : vec)
{
cout << i << "";
}
cout << endl;
for (int j : vecx)
{
cout << j << "";
}
return 0;
}
------------------------------------------------------------------------------------------------------
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-kpilswng.fil --stdout=Microsoft-MIEngine-Out-lllfevgw.4pb --stderr=Microsoft-MIEngine-Error-f4342yjf.phl --pid=Microsoft-MIEngine-Pid-edkkjwrg.zlu "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
01234567891235354
56789
E:\Users\86184\Documents\Code>
本帖最后由 bin554385863 于 2019-8-20 01:20 编辑
2019年8月20日01:08:15
#include <iostream>
#include <initializer_list>
#include <vector>
/*可变参数类型std::initializer_list<type>*/
double sum(const std::initializer_list<int> &arg)
{
double sum = 0;
for(int num: arg)
{
sum += num;
}
return sum;
}
int main(int argc, char const *argv[])
{
const int a = 10;
const int b = 100, c = 63;
/*std::initializer_list<int>参数列表元素全都是常量*/
std::cout<<sum({1,-2,3,-4,5,-6,7,-8,9})<<std::endl;
std::cout<<sum({a, b, c});
return 0;
}
------------------------------------------------------------------------------------
E:\Users\86184\Documents\Code>cmd /C "c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.24.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-dpp1rvoj.vjf --stdout=Microsoft-MIEngine-Out-0knopx0m.s0l --stderr=Microsoft-MIEngine-Error-nrqlrhy2.j3p --pid=Microsoft-MIEngine-Pid-0nqtwlhr.btm "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi "
5
110
E:\Users\86184\Documents\Code> 2019年8月24日01:44:22
#include <iostream>
#include <string>
#include <vector>
#include<algorithm>
using namespace std;
/*编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""*/
string longestCommonPrefix(vector<string> &strs)
{
string result;
/*判定向量是否为空*/
if (strs.empty())
{
result = {""};
}
else
{
size_t s = strs.size();
if (s == 1)
{
return strs;
}
else if (s > 1)
{
/*构造向量字符串元素跳过对{"c", "c",...}和{"","",....}类似的向量元素的判定*/
strs = strs + "10lo";
strs = strs + "l01o";
/*初始化计数器*/
size_t count = 0;
/*定义数组,保存对比的计数结果*/
vector<int> arr;
for (size_t i = s - 1; i > 0; i--)
{
for (size_t j = 0;; j++)
{
if (strs != strs)
{
break;
}
count++;
}
arr.push_back(count);
/*重新初始化计数器*/
count = 0;
}
size_t sa = arr.size();
/*从小到大排序*/
sort(arr.begin(), arr.end());
/*首位元素就是最终结果*/
if (arr == 0)
{
result = {""};
}
else
{
for (size_t i = 0; i < arr; i++)
{
result.push_back(strs);
}
}
}
}
return result;
}
int main(int argc, char const *argv[])
{
vector<string> a = {"boot", "boom", "boor", "boall"};
vector<string> b = {"baac", "bacc", "b"};
vector<string> c = {"flower"};
vector<string> d = {"",""};
vector<string> e =
{
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
};
cout << longestCommonPrefix(a) << endl;
cout << longestCommonPrefix(b) << endl;
cout << longestCommonPrefix(c) << endl;
cout << longestCommonPrefix(d) << endl;
cout << longestCommonPrefix(e) << endl;
return 0;
}
-------------------------------------------------------------------------------------------------------------------
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.25.0\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-2uug3wdg.re0 --stdout=Microsoft-MIEngine-Out-fgrkj5jj.np0 --stderr=Microsoft-MIEngine-Error-hm0qrys1.x3k --pid=Microsoft-MIEngine-Pid-nxksjd20.xqb "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
bo
b
flower
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
E:\Users\86184\Documents\Code> 2019年8月25日20:05:53
委托构造函数定义
function():func(0, 0,....){}
无参数构造函数 func()委托有参数构造函数funct(args, ....),构造对象
复制构造函数
classname(args,...);---默认构造函数
classname(const classname &objectname);------复制构造函数
classname(const classname &objectname) = delete;-----禁止复制构造
2019年8月28日01:23:54
一个完整的类的定义
#include <iostream>
class clock
{
private:
/*成员变量*/
int Hour, Minute, Second;
public:
/*有参数的构造函数*/
clock(int hour, int minute, int second) : Hour(hour), Minute(minute), Second(second) {}
/*默认构造函数委托有参数的构造函数初始化*/
clock() : clock(0, 0, 0) {}
/*复制构造函数*/
clock(const clock &oarg);
/*声明析构函数.当对象销毁时系统会自动调用析构函数做最后的清理*/
~clock();
/*声明成员函数*/
void setClock(int hour, int minute, int second);
void showClock();
};
/*定义复制构造函数*/
clock::clock(const clock &oarg)
{
Hour = oarg.Hour;
Minute = oarg.Minute;
Second = oarg.Second;
}
/*定义成员函数*/
void clock::setClock(int hour, int minute, int second)
{
Hour = hour;
Minute = minute;
Second = second;
}
void clock::showClock()
{
std::cout << Hour << ":" << Minute << ":" << Second << std::endl;
}
/*定义析构函数*/
clock::~clock()
{
std::cout<<"调用析构函数"<<std::endl;
}
int main(int argc, char const *argv[])
{
/*若没有定义有参数的构造函数,下面的语句就会报错*/
clock a = {2, 3, 6};
/*若自己没有定义无参数的构造函数,则编译器会自己添加一个默认的构造函数*/
clock b;
/*调用成员函数*/
a.showClock();
b.showClock();
b.setClock(24, 20, 59);
b.showClock();
clock c = b;
c.showClock();
return 0;
}
-----------------------------------------------------------------------------------------------------
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.25.0\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-iywbbnha.vug --stdout=Microsoft-MIEngine-Out-oyslqpxc.ymz --stderr=Microsoft-MIEngine-Error-nzoewxi5.lo3 --pid=Microsoft-MIEngine-Pid-kt3fdbr2.5b2 "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
a = 2:3:6
b = 0:0:0
重新设置b = 24:20:59
c = 24:20:59
调用析构函数
调用析构函数
调用析构函数
E:\Users\86184\Documents\Code> 2019年8月29日21:01:11
类的组合
#include <iostream>
#include <cmath>
class dot
{
private:
int Y, X;
public:
//构造
dot(int x = 0, int y = 0) : X(x), Y(y) {}
//复制构造
dot(const dot &tdot) : X(tdot.X), Y(tdot.Y) {}
int getX()
{
return X;
}
int getY()
{
return Y;
}
void setdot(int x, int y)
{
Y = y;
X = x;
}
void showdot()
{
std::cout << "(" << X << " , " << Y << ")" << std::endl;
}
};
class line
{
private:
dot Start, End;
double Length;
public:
line(dot start, dot end) : Start(start), End(end)
{
double X = end.getX() - start.getX();
double Y = end.getY() - start.getY();
Length = sqrt(pow(X, 2) + pow(Y, 2));
}
line(const line &tline) : Start(tline.Start), End(tline.End)
{
Length = tline.Length;
}
void length()
{
std::cout << Length << std::endl;
}
};
int main(int argc, char const *argv[])
{
dot start = {5, 9}, end = {9, 20};
line aline = {start, end};
aline.length();
line kline = aline;
kline.length();
return 0;
}
---------------------------------------------------------------------------------------------------------
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.25.0\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-ofgnd5qn.5eu --stdout=Microsoft-MIEngine-Out-2zgryx00.0rd --stderr=Microsoft-MIEngine-Error-rvusglds.1ia --pid=Microsoft-MIEngine-Pid-ww5hv3ry.sbq "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
11.7047
11.7047
E:\Users\86184\Documents\Code> 本帖最后由 bin554385863 于 2019-9-4 00:14 编辑
2019年9月4日00:09:00
类的继承
#include <iostream>
/*父类*/
class shape
{
protected:
unsigned int H, L;
public:
int area()
{
return H * L;
}
};
//子类
class rect : public shape
{
public:
rect(int l = 0, int h = 0)
{
L = l;
H = h;
}
rect(const rect &r)
{
L = r.L;
H = r.H;
}
~rect()
{
std::cout << "子类不能使用初值列初始化构造函数" << std::endl;
};
};
int main(int argc, char const *argv[])
{
rect r1;
std::cout <<"r1.area = "<< r1.area() << std::endl;
rect r2 = {9, 6};
std::cout <<"r2.area = "<< r2.area() << std::endl;
return 0;
}
-----------------------------------------------------------------------
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.25.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-eq2d2nqk.0np --stdout=Microsoft-MIEngine-Out-i5vlflfq.qos --stderr=Microsoft-MIEngine-Error-yp3hcvsu.fap --pid=Microsoft-MIEngine-Pid-csr0yko2.w4f "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
r1.area = 0
r2.area = 54
子类不能使用初值列初始化构造函数
子类不能使用初值列初始化构造函数
E:\Users\86184\Documents\Code>
-----------------------------------------------------------------------------------------------------
---
子类拥有父类的一切非私有数据和函数
---
公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
---
更正:
子类不能使用父类的数据成员用作初值列初始化子类构造函数 2019年9月4日23:42:54
运算符重载
#include <iostream>
#include <cmath>
class sinx_y
{
private:
double X;
public:
sinx_y(double x = 0) : X(x) {}
sinx_y(const sinx_y &s) : X(s.X) {}
sinx_y operator+(const sinx_y &y)
{
sinx_y result;
result.X = this->X * sqrt(1 - y.X * y.X) + sqrt(1 - this->X * this->X) * y.X;
return result;
}
sinx_y operator-(const sinx_y &y)
{
sinx_y result;
result.X = this->X * sqrt(1 - y.X * y.X) - sqrt(1 - this->X * this->X) * y.X;
return result;
}
double showresult()
{
return X;
}
};
int main(int argc, char const *argv[])
{
sinx_y x = 0.5, y = sqrt(3.0) / 2;
sinx_y z = x + y;
std::cout << "sin(x+y) = " << z.showresult() << std::endl;
sinx_y k = y - x;
std::cout << "sin(x-y) = " << k.showresult() << std::endl;
return 0;
}
-----------------------------------------------------------------------------------------------
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.25.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-jug5q2wo.xhj --stdout=Microsoft-MIEngine-Out-3r2wfnyi.ee5 --stderr=Microsoft-MIEngine-Error-obkgmcpp.fg3 --pid=Microsoft-MIEngine-Pid-yyvklzxr.whs "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
sin(x+y) = 1
sin(x-y) = 0.5
E:\Users\86184\Documents\Code>
--------------------------------------------------------------------------------------
下面是可重载的运算符列表:
+ - * / % ^
& | ~ ! , =
< > <= >= ++ --
<< >> == != && ||
+= -= /= %= ^= &=
|= *= <<= >>= [] ()
-> ->* new new [] delete delete []
下面是不可重载的运算符列表:
:: .* . ?:
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。 2019年9月8日23:37:20
虚函数-继承-多态
关注问题:
虚函数的作用
虚函数的实现原理
虚函数表在对象布局里的位置
虚函数的类的sizeof
纯虚函数的作用
多级继承时的虚函数表内容
虚函数如何执行父类代码
多继承时的虚函数表定位,以及对象布局
虚析构函数的作用
虚函数在QT的信号与槽中的应用
虚函数与inline修饰符,static修饰符
前面我们尝试了一个简单的例子,接下来尝试一个多级继承的例子,以及一个多继承的例子。主要涉及到以下问题:多级继承时虚函数表的内容是如何填写的,如何在多级继承的情况下调用某一级父类里的虚函数,以及在多继承(多个父类)的情况下的对象布局。
多级继承
在这里,多级继承指的是有3层或者多层继承关系的情形。让我们看看下面的代码:
多层继承代码示例
//Source filename: Win32Con.cpp
#include <iostream>
using namespace std;
class parent1
{
public:
virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};
virtual int fun2()=0;
};
class child1:public parent1
{
public:
virtual int fun1()
{
cout<<"child1::fun1()"<<endl;
parent1::fun1();
return 0;
}
virtual int fun2()
{
cout<<"child1::fun2()"<<endl;
return 0;
}
};
class grandson:public child1
{
public:
virtual int fun2()
{
cout<<"grandson::fun2()"<<endl;
//parent1::fun2();
parent1::fun1();
child1::fun2();
return 0;
}
};
void test_func1(parent1 *pp)
{
pp->fun1();
pp->fun2();
}
int main(int argc, char* argv[])
{
grandson sunzi;
test_func1(&sunzi);
return 0;
}
这段代码展示了三个class,分别是parent1,child1,grandson。
类parent1定义了两个虚函数,其中fun2是一个纯虚函数,这个类是一个不可实例化的抽象基类。
类child1继承了parent1,并且对两个虚函数fun1和fun2都编写了实现的代码,这个类可以被实例化。
类grandson继承了child1,但是只对虚函数fun2编写了实现的代码。
此外,我们还改写了test_func1函数,它的参数为parent1类型的指针,我们可以将parent1的子孙类作为这个函数的参数传入。在这个函数里,我们将依次调用parent1类的两个虚函数。
可以先通过阅读代码预测一下程序的输出内容。
程序的输出内容将是:
child1::fun1()
parent1::fun1()
grandson::fun2()
parent1::fun1()
child1::fun2()
先看第一行输出child1::fun1(),为什么会输出它呢?我们定义的具体对象sunzi是grandson类型的,test_func1的参数类型是parent1类型。在调用这个虚函数的时候,是完成了一次怎样的调用过程呢?
让我们再次使用cl命令输出这几个类的对象布局:
class parent1 size(4):
+---
0 | {vfptr}
+---
parent1::$vftable@:
| &parent1_meta
|0
0 | &parent1::fun1
1 | &parent1::fun2
parent1::fun1 this adjustor: 0
parent1::fun2 this adjustor: 0
class child1 size(4):
+---
| +--- (base class parent1)
0 | | {vfptr}
| +---
+---
child1::$vftable@:
| &child1_meta
|0
0 | &child1::fun1
1 | &child1::fun2
child1::fun1 this adjustor: 0
child1::fun2 this adjustor: 0
class grandsonsize(4): //grandson的对象布局
+---
| +--- (base class child1)
| | +--- (base class parent1)
0 | | | {vfptr}
| | +---
| +---
+---
grandson::$vftable@://grandson虚函数表的内容
| &grandson_meta
|0
0 | &child1::fun1
1 | &grandson::fun2
grandson::fun2 this adjustor: 0
因为我们实例化的是一个grandson对象,让我们看看它的对象布局。正如前面的例子一样,里面只有一个vfptr指针,但是不一样的却是这个指针所指的虚函数表的内容:
第一个虚函数,填写的是child1类的fun1的地址;第二个虚函数填写的才是grandson类的fun2的地址。
很显然我们可以得出这样一个结论:在一个子对象的虚函数表里,每一个虚函数的实际运行的函数地址,将填写为在继承体系里最后实现该虚函数的函数地址。
所以当我们在test_func1里调用了传入的parent1指针的fun1函数的时候,我们实际执行的是填写在虚函数表里的 child1::fun1(),而调用fun2函数的时候,是从虚函数表里得到了grandson::fun2函数的地址并调用之。在“程序输出结果”表里的第一行和第三行结果证实了上述结论。
再看一下程序代码部分的child1::fun1()的实现代码,在第18行,我们有parent1::fun1();这样的语句,这行代码输出了运行结果里的第二行,而在grandson::fun2()的实现代码第35行的parent1::fun1();以及第36行的child1::fun2();则输出了运行结果里的第四行和第五行的内容。这三行代码展示了如何调用父类以及更高的祖先类里的虚函数。——事实上,这与调用父类的普通函数没有任何区别。
在程序代码的第34行,有一行被注释了的内容//parent1::fun2();,之所以会注释掉,是因为这样的代码是无法通过编译的,因为在parent1类里,fun2是一个“纯虚函数”也就是说这个函数没有代码实体,在编译的时候,链接器将无法找到fun2的目标代码从而报错。
其实有了对虚函数的正确的认识,上面的多级继承是很自然就能明白的。然而在多继承的情况下,情况就有所不同了。。。
多继承下虚函数的使用
假如一个类,它由多个父类继承而来,而在不同的父类的继承体系里,都存在虚函数的时候,这个类的对象布局又会是怎样的?它又是怎样定位虚函数的呢?
让我们看看下面的代码:
程序输出结果
//Source filename: Win32Con.cpp
#include <iostream>
using namespace std;
class parent1
{
public:
virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};
};
class parent2
{
public:
virtual int fun2(){cout<<"parent2::fun2()"<<endl;return 0;};
};
class child1:public parent1,public parent2
{
public:
virtual int fun1()
{
cout<<"child1::fun1()"<<endl;
return 0;
}
virtual int fun2()
{
cout<<"child1::fun2()"<<endl;
return 0;
}
};
void test_func1(parent1 *pp)
{
pp->fun1();
}
void test_func2(parent2 *pp)
{
pp->fun2();
}
int main(int argc, char* argv[])
{
child1 chobj;
test_func1(&chobj);
test_func2(&chobj);
return 0;
}
这一次,我们有两个父类,parent1和parent2,在parent1里定义了虚函数fun1,而在parent2里定义了虚函数fun2,然后我们有一个子类child1,在里面重新实现了fun1和 fun2两个虚函数。然后我们编写了test_func1函数来调用parent1类型对象的fun1函数,编写了test_func2函数调用 parent2对象的fun2函数。在main函数里我们实例化了一个child1类型的对象chobj,然后分别传给test_func1和 test_func2去执行。
这段代码的运行结果非常简单就能看出来:
child1::fun1()
child1::fun2()
但是,让我们看看对象布局吧:
class child1 size(8):
+---
| +--- (base class parent1)
0 | | {vfptr}
| +---
| +--- (base class parent2)
4 | | {vfptr}
| +---
+---
child1::$vftable@parent1@:
| &child1_meta
|0
0 | &child1::fun1
child1::$vftable@parent2@:
| -4
0 | &child1::fun2
child1::fun1 this adjustor: 0
child1::fun2 this adjustor: 4
注意到没?在child1的对象布局里,出现了两个vfptr指针!
这两个虚函数表指针分别继承于parent1和parent2类,分别指向了不同的两个虚函数表。
问题来了,当我们使用test_func1调用parent1类的fun1函数的时候,调用个过程还比较好理解,可以从传入的地址参数取得继承自 parent1的vfptr,从而执行正确的fun1函数代码,但是当我们调用test_func2函数的时候,为什么程序可以自动取得来自 parent2的vfptr呢,从而得出正确的fun2函数的地址呢?
其实,这个工作是编译器自动根据实例的类型完成的,在编译阶段就已经确定了在调用test_func2的时候,传入的this指针需要增加一定的偏移(在这里则是第一个vfptr所占用的大小,也就是4字节)。
我们可以看看main函数里这部分代码的反汇编代码:
反汇编代码
child1 chobj;
00F5162E 8D 4D F4 lea ecx,
00F51631 E8 F5 FB FF FF call child1::child1 (0F5122Bh)
test_func1(&chobj);
00F51636 8D 45 F4 lea eax,
00F51639 50 push eax
00F5163A E8 6F FB FF FF call test_func1 (0F511AEh)
00F5163F 83 C4 04 add esp,4
test_func2(&chobj);
00F51642 8D 45 F4 lea eax,
00F51645 85 C0 test eax,eax
00F51647 74 0E je main+47h (0F51657h)
00F51649 8D 4D F4 lea ecx,
00F5164C 83 C1 04 add ecx,4
00F5164F 89 8D 2C FF FF FF mov dword ptr ,ecx
00F51655 EB 0A jmp main+51h (0F51661h)
00F51657 C7 85 2C FF FF FF 00 00 00 00 mov dword ptr ,0
00F51661 8B 95 2C FF FF FF mov edx,dword ptr
00F51667 52 push edx
00F51668 E8 F6 FA FF FF call test_func2 (0F51163h)
00F5166D 83 C4 04 add esp,4
return 0;
从第4行至第5行,执行的是test_func1函数,this指针指向 chobj (第2行lea ecx,),但是调用test_func2函数的时候,this指针被增加了4(第14行)!于是,在test_func2执行的时候,就可以从&chobj+4的地方获得vfptr指针,从而根据parent2的对象布局得到了fun2的地址并执行了。
为了证实这点,我们可以将代码做如下的修改:
1:int main(int argc, char* argv[])
2:{
3: child1 chobj;
4: test_func1(&chobj);
5: test_func2((parent2 *)(void *)&chobj);
6: return 0;
7:}
8:
请注意红色部分的变化,在讲chobj传入给test_func2之前,先用(void *)强制转换为无类型指针,再转换为parent2 指针,这样的转换,显然是可行的,因为chobj本身就是parent2的子类,然而,程序的执行效果却是:
child1::fun1()
child1::fun1()
执行test_func2函数,调用的是parent2::fun2,但是居然执行的是child1::fun1()函数!!!
这中间发生了些什么呢?我们再看看反汇编的代码:
反汇编的代码
child1 chobj;
013D162E 8D 4D F4 lea ecx,
013D1631 E8 F5 FB FF FF call child1::child1 (13D122Bh)
test_func1(&chobj);
013D1636 8D 45 F4 lea eax,
013D1639 50 push eax
013D163A E8 6F FB FF FF call test_func1 (13D11AEh)
013D163F 83 C4 04 add esp,4
test_func2((parent2*)(void *)&chobj);
013D1642 8D 45 F4 lea eax,
013D1645 50 push eax
013D1646 E8 18 FB FF FF call test_func2 (13D1163h)
013D164B 83 C4 04 add esp,4
return 0;
从调用test_func2的反汇编代码可以看到,这一次ecx寄存器的值没有做改变!所以在执行test_func2的时候,将取得 parent1对象布局里的vfptr,而这个vfptr所指的虚函数表里的第一项就是fun1,并且被填写为child1::fun1的地址了。所以才出现了child::fun1的输出内容!显然这里有一个隐藏的致命问题,加入parent1和parent2的第一个虚函数的参数列表不一致,这样的调用显然就会导致堆栈被破坏掉,程序99%会立即崩溃。之前的程序没有崩溃并且成功输出内容,不过是因为parent1::fun1()和 parent2::fun2()的参数列表一致的关系而已。
所以,千万不要在使用一个多继承对象的时候,将其类型信息丢弃,编译器还需要依靠正确的类型信息,在使用虚函数的时候来得到正确的汇编代码!
多继承与虚函数重复
既然说到了多继承,那么还有一个问题可能会需要解决,那就是如果两个父类里都有相同的虚函数定义,在子对象的布局里会是怎么样个情况?是否依然可以将这个虚函数指向到正确的实现代码上呢?
修改前面一个源代码,在parent2的接口里增加下面的虚函数定义:
virtual int fun1(){cout<<"parent2::fun1()"<<endl;return 0;};
上面的fun1的定义与parent1类里的完全重复相同(类型,参数列表),增加上面的代码后立即开始编译,程序正常编译通过。运行之,得到下面的结果:
child1::fun1()
child1::fun2()
这个程序居然正确的完成了执行,编译器在其中做了些怎样的工作,是怎么样避免掉队fun1函数的冲突问题呢?
让我们来看看这个时候的child1的对象布局:
class child1 size(8):
+---
| +--- (base class parent1)
0 | | {vfptr}
| +---
| +--- (base class parent2)
4 | | {vfptr}
| +---
+---
child1::$vftable@parent1@:
| &child1_meta
|0
0 | &child1::fun1
child1::$vftable@parent2@:
| -4
0 | &child1::fun2
1 | &thunk: this-=4; goto child1::fun1
child1::fun1 this adjustor: 0
child1::fun2 this adjustor: 4
恩~~~还是两个vfptr在child1的对象布局里(不一样就怪啦,呵呵),但是第二个vfptr所指的虚函数表的内容有所变化哦!
注意看红色字体部分,虚函数表里并没有直接填写child::fun1的代码,而是多了一个 &thunk: this-=4;然后才goto child1::fun1!注意到一个关键名词thunk了吧?没错,vc在这里使用了名为thunk的技术,避免了虚函数fun1在两个基类里重复出现导致的冲突问题!(除了thunk,还有其他方法可以解决此类问题的)。
现在,我们知道为什么相同的虚函数不会在子类里出现冲突的情况了。
但是,倘若我们在基类里就是由两个冲突的普通函数,而不是虚函数,是个怎样的情况呢?
多继承产生的冲突与虚继承,虚基类
我们在parent1和parent2里添加一个相同的函数void fun3(),然后再进行编译,通过了!查看类对象布局,跟上面的完全一致。但是在main函数里调用chobj.fun3()的时候,编译器却不再能正确编译了,并且会提示“error C2385: 对“fun3”的访问不明确”的错误信息,没错,编译器不知道你要访问哪个fun3了。
如何解决这样的多继承带来的问题呢,其实有一个简单的做法。就是在方法前限定引用的具体是哪个类的函数,比如:chobj.parent1::fun3();,这样的写法就写明了是要调用chobj的父类parent1里的fun3()函数!
我们再看看另外一种情况,从parent1和parent2里抹去刚才添加的fun3函数,将之放到一个共同的基类里:
class commonbase
{
public:
void fun3(){cout<<"commonbase::fun3()"<<endl;}
};
而parent1和parent2都修改为从此类继承。可以看到,在这个情况下,依然需要使用chobj.parent1::fun3();的方式才可以正确调用到fun3,难道,在这种情况下,就不能自然的使用chobj.fun3()这样的方式了吗?
虚继承可以解决这个问题——我们在parent1和parent2继承common类的地方添加上一个关键词virtual,如下:
class parent1:virtual public commonbase
{
public:
virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};
};
给parent2也同样的处理,然后再次编译,这次chobj.fun3()可以编译通过了!!!
编译器这次又在私下里做了哪些工作了呢????
class child1 size(16):
+---
| +--- (base class parent1)
0 | | {vfptr}
4 | | {vbptr}
| +---
| +--- (base class parent2)
8 | | {vfptr}
12 | | {vbptr}
| +---
+---
+--- (virtual base commonbase)
+---
child1::$vftable@parent1@:
| &child1_meta
|0
0 | &child1::fun1
child1::$vftable@parent2@:
| -8
0 | &child1::fun2
1 | &thunk: this-=8; goto child1::fun1
child1::$vbtable@parent1@:
0 | -4
1 | 12 (child1d(parent1+4)commonbase)
child1::$vbtable@parent2@:
0 | -4
1 | 4 (child1d(parent2+4)commonbase)
child1::fun1 this adjustor: 0
child1::fun2 this adjustor: 8
vbi: classoffset o.vbptro.vbte fVtorDisp
commonbase 16 4 4 0
这次变化可大了去了!!!
首先,可以看到两个类parent1和parent2的对象布局里,都多了一个vbptr的指针。而在child1的对象布局里,还有一个virtual base commonbase的虚拟基类。再看看两个vbptr的内容:
12 (child1d(parent1+4)commonbase) 这个很好理解,从parent1的vbptr开始,偏移12个字节,指向的是virtual base commonbase!
再看看4 (child1d(parent2+4)commonbase) ,从parent2的vbptr开始,便宜4个字节,也指向了virtual base commonbase!
这下明白了。虚基类在child1里只有一个共同的对象布局了,所以就可以直接用chobj.fun3()啦,当然,在commonbase里的其他成员变量此时也可以同样的方式访问了!
虽然解决方案有了,但是在一个系统的设计里,如果有一个基类出现多继承冲突的情况,大部分情况下都说明这样的设计是有问题的,应该尽量避免这样的设计,并且尽量用纯虚函数,来提取一些抽象的接口类,把共同的方法接口都抽取出来,通常就能避免多继承的问题。 本帖最后由 bin554385863 于 2019-9-10 22:51 编辑
2019年9月10日18:43:35
.组合
类的成员中出现其他类的对象,叫做类的组合.
.继承
.虚继承:
父类:A;
子类B , C继承于A;
子类D继承于B, C;
则B, C, 需要使用虚继承,避免D中出现重复的A.
即
class A {...}
class B: virtual public A {...}
class C: virtual public A {...}
class D: public B, public C {...}
.虚函数
当多个子类需要改写父类的成员函数时, 父类中对应的成员函数需要声明为虚函数
.虚函数的特点
关于虚函数的特点:
(1)要有子类公有继承父类,虚函数才有意义
(2)子类重写的父类的虚函数也是虚函数(类Dog中的eat函数是虚函数),只不过省略了关键字virtual
(3)虚函数必须是所在类的成员函数,而不能是友元函数或静态函数。因为虚函数调用要靠特定的对象来决定激活哪一个函数。
(4)构造函数不能是虚函数,原因是构造函数在创建时首先被调用,也就是说在编译时就要把构造函数的地址绑定好。而析构函数可以是虚函数,因为析构函数最后调用,可以在运行时动态匹配。
基类定义了虚函数, 并且是public的,那么子类只要override 虚函数 无论放在什么样的访问权限下(private,protect,public), 都以基类的访问权限为准, 即是public的。
.虚析构
把析构函数设为虚函数主要是为了防止内存泄漏
我们首先要知道的事实(单继承时):
构造函数的调用顺序:从当前类往上找父类,一直找到最上层的父类,我们可以理解为始祖,它是最先构造的,然后沿着继承路径依次往下构造,一直到当前类。
析构函数的调用顺序:从当前类开始析构,析构完再沿着继承路径往上找父类 ,析构父类 ,再找到最上层的父类 析构
.虚函数的默认参数
虚函数是动态绑定的,而虚函数的默认参数是静态绑定的,因此在编译时已经把虚函数的参数的值确定了,而不管在后面执行时动态调用哪个虚函数改变的值
。
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A的构造函数" << endl;
}
~A()
{
cout << "A的析构函数" << endl;
}
};
class B:public A
{
public:
B()
{
p = new char;
cout << "B的构造函数" << endl;
}
~B()
{
if (p != NULL)
delete[] p;
cout << "B的析构函数" << endl;
}
private:
char *p;
};
void func(A *pa)
{
delete pa;
}
int main()
{
A *pa = new B; //构造函数被调用两次,A先B后
func(pa); //析构函数只被调用一次,A调用
return 0;
}
----------------------------------------------------------------------
————————————————
版权声明:本文为CSDN博主「qq_1061856980華仔」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hua12134/article/details/79780419
本帖最后由 bin554385863 于 2019-9-11 11:27 编辑
2019年9月11日00:55:53
虚函数,虚继承
#include <iostream>
class animal
{
public:
animal()
{
std::cout << "父类构造" << std::endl;
}
void eat()
{
std::cout << "父类成员函数eat()" << std::endl;
}
virtual void sleep()
{
std::cout << "父类成员函数sleep()" << std::endl;
}
virtual ~animal()
{
std::cout << "父类析构函数" << std::endl;
}
};
class dog : virtual public animal
{
public:
dog()
{
std::cout << "子类dog构造函数" << std::endl;
}
virtual void sleep()
{
std::cout << "子类dog成员函数sleep()" << std::endl;
}
virtual ~dog()
{
std::cout << "子类dog析构函数" << std::endl;
}
};
class pig : virtual public animal
{
public:
pig()
{
std::cout << "子类pig构造函数" << std::endl;
}
virtual void sleep()
{
std::cout << "子类pig成员函数sleep()" << std::endl;
}
virtual ~pig()
{
std::cout << "子类pig析构函数" << std::endl;
}
};
class ppg : public dog, public pig
{
public:
ppg()
{
std::cout << "ppg构造函数" << std::endl;
}
virtual void sleep()
{
std::cout << "ppg成员函数sleep()" << std::endl;
}
virtual ~ppg()
{
std::cout << "ppg析构函数" << std::endl;
}
};
int main(int argc, char const *argv[])
{
ppg p;
animal *ppg = &p;
ppg->sleep();
ppg->eat();
return 0;
}
---------------------------------------------------------------------------------------------------------------
Microsoft Windows [版本 10.0.16299.1087]
(c) 2017 Microsoft Corporation。保留所有权利。
E:\Users\86184\Documents\Code>c:\Users\86184\.vscode\extensions\ms-vscode.cpptools-0.25.1\debugAdapters\bin\WindowsDebugLauncher.exe --stdin=Microsoft-MIEngine-In-2i0pz4rl.ub2 --stdout=Microsoft-MIEngine-Out-1z1pnj4a.tq0 --stderr=Microsoft-MIEngine-Error-5ykprrfo.1a2 --pid=Microsoft-MIEngine-Pid-ievsqwkx.vhs "--dbgExe=E:\My Program\MinGW\bin\gdb.exe" --interpreter=mi
父类构造
子类dog构造函数
子类pig构造函数
ppg构造函数
ppg成员函数sleep()
父类成员函数eat()
ppg析构函数
子类pig析构函数
子类dog析构函数
父类析构函数
E:\Users\86184\Documents\Code>
-----------------------------------------------------------------------
页:
[1]