Skip to content

头文件

但唯独string特别。 问题在于C++要兼容C的标准库,而C的标准库里碰巧也已经有一个名字叫做“string.h”的头文件,包含一些常用的C字符串处理函数,比如楼主提到的strcmp。 这个头文件跟C++的string类半点关系也没有,所以<string>并非<string.h>的“升级版本”,他们是毫无关系的两个头文件。

函数重载

c++
int a(int b=1 , int c =1 ,int d=1)

int a(int b=1 , int c =1 )

a(6,6,6)

a(6,6)
    //会报错,a(6,6)存在歧义,不知道调用哪个
    有默认值的话才会冲突,要是没默认值的话就没事
    没有默认值的话会根据传入参数数选择函数
c++
int a(int b=1 , int c =1 ,int d=1)
    a(6,6)
    这样后 b=6,c=6,d=1
    参数补齐是从左到右,加默认参数是从右往左
c++
int a(int b , int c ,int d)
    a(6,6)
    报错,因为我们在编译就会生成指令,现在只传了两个参数,那生成的三条指令中,剩下的一条指令不知道要把谁给push到栈里所以会出错。

格式化输出

c++
保留两位小数
<< fixed << setprecision(2)<<
    #include <iomanip>
    
<< setiosflags(ios::left) <<  // 左对齐
<< setw(2) << "学号" << "  " // 输出宽度为 2

指针传递数组

c++
    int n[5];
    for (int i = 0; i < 5; i++)
    {
        cin >> n[i];
    }
    int *p = n; //在这里赋值为数组n的地址
    for (int i = 0; i < 5; i++)
    {
        cout << p[i] << " ";
    }
例子 传递数组
#include <iostream>
#include <iomanip>

using namespace std;

class mian
{
public:
   int a=0;
};
void output(int* a)
{
    cout<<a<<endl;

    cout<<a[0]<<endl;
    cout<<&a[0]<<endl;

    cout<<a[1]<<endl;
    cout<<&a[1]<<endl;
    cout<<a[2]<<endl;
}
int main ()
{
    int a[10];
    a[0]=1;
    a[1]=2;
    a[2]=3;
    output(a);
    cout<<a<<endl;
    return 0;
}
传递类数组
#include <iostream>
#include <iomanip>

using namespace std;

class test
{
public:
   int a=0;
};
void output(test *a)
{
    cout<<a<<endl;
    cout<<a->a<<endl;
}
int main ()
{
    test a[10];

    output(a);
    cout<<a<<endl;
    return 0;
}

方式 1

形式参数是一个指针:

c++
void myFunction(int *param)
{
.
.
.
}

方式 2

形式参数是一个已定义大小的数组:

c++
void myFunction(int param[10])
{
.
.
.
}

方式 3

形式参数是一个未定义大小的数组:

c++
void myFunction(int param[])
{
.
.
.
}

引用类型

c++
int a;

int &b=a;

这就表明了b是a的"引用",即a的别名。经过这样的声明,使用a或b的作用相同,都代表同一变量。在上述引用中,&是"引用声明符",并不代表地址。

不要理解为"把a的值赋给b的地址"。

在本函数执行期间,该引用一直与其代表的变量相联系,不能再作为其他变量的别名。

引用是不能传递数组的,他可以传递类,类的对象,但并不能传递数组

引用和指针的区别

**看实例吧:

引用是C++中的概念,初学者容易把引用和指针混淆一起。

下面的程序中,n是m的一个引用(reference),m是被引用物(referent)。

c++
int m;

int &n = m;

n相当于m的别名(绰号),对n的任何操作就是对m的操作。

所以n既不是m的拷贝,也不是指向m的指针,其实n就是m它自己。

c++
#include<iostream>
#include<string>
using namespace std; 

 //1.请完成swapByRef函数,利用引用形参实现两个整数值的交换(不要删除该行注释) 
int swapByRef(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
    return 0;
}
 //2.请完成swapByPoint函数,利用指针形参实现两个整数值的交换(不要删除该行注释) 
int swapByPoint(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
    return 0;
}
 //3.请完成swapByValue函数,利用值形参实现两个整数值的交换(不要删除该行注释)
//注意:不能改动main函数 

int main()
{
    int a, b;
    cin>>a>>b;
    swapByRef(a,b); //调用swapByRef函数,交换a和b的值
    cout<<"a:"<<a<<" b:"<<b<<endl;
    cin>>a>>b;
    swapByPoint(&a,&b); //调用swapByPoint函数,交换a和b的值
    cout<<"a:"<<a<<" b:"<<b<<endl;  
    return 0;
}
c++
编写一个函数mySum,其功能是计算一个数组中的所有奇数之和与偶数之和。
 函数原型可参考如下:
void mySum(int *p, int len, int &sumOdd, int &sumEven);
其中,p为指向数组首元素的指针,len为数组中的元素个数,sumOdd和sumEven为引用参数,分别用于存放数组中偶数和与奇数和。
 如,数组中的原始数据为:1034025。则函数计算的奇数之和为9,偶数之和为6。
 编写程序,从键盘输入一个正整数n(1<=n<=100),代表数据元素个数,接着输入n个整数,代表数组元素的初始值,调用mySum函数处理完毕后,在主函数中输出计算出的奇数之和与偶数之和。输出的两个数据之间用空格分隔。

#include <iostream>
#include <string>
#include <cstring>
using namespace std;

void mySum(int *p, int len, int &sumOdd, int &sumEven)
{
    for(int i=0;i<len;i++)
    {
        if(*(p+i)%2==0)
        {
            sumOdd+=*(p+i);
        } else {
            if(*(p+i)%2==1)
            {
                sumEven+=*(p+i);
            }
        }
    }
}
int main()
{
    int n, *a,sumOdd=0,sumEven=0;
    cin>>n;
    a = new int[n];
    for (int i = 0; i < n; i++)
    {
    cin>>a[i];
    }
    mySum(&a[0],n,sumOdd,sumEven);
    cout<<sumEven<<" "<<sumOdd;
    

    return 0;
}

异常处理

c++
try
{
   // 保护代码
}catch(...)
{
  // 能处理任何异常的代码
}
遇到异常后立刻跳转,异常前代码正常执行,后代码不再执行



#include <iostream>
using namespace std;
 
double division(int a, int b)
{
   if( b == 0 )
   {
      throw "Division by zero condition!";
   }
   return (a/b);
}
 
int main ()
{
   int x = 50;
   int y = 0;
   double z = 0;
 
   try {
     z = division(x, y);
     cout << z << endl;
   }catch (const char* msg) {
     cerr << msg << endl;
   }
 
   return 0;
}
c++
#include <iostream>
#include <iomanip>
#include <cstring>
#include <string.h>
using namespace std;
int main()
{
    int n,a,b;
    char c[100][80], temp[80];
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin>>c[i];
    }
    for (int i = 0; i < n-1; i++)
    {
        for (int i_one = 0;i_one < n-1; i_one++)
        {

            if (strcmp(c[i_one], c[i_one + 1])>0)
            {
                strcpy(temp , c[i_one + 1]);
                strcpy(c[i_one + 1] , c[i_one]);
                strcpy(c[i_one] , temp);
            }
        }
    }
    for (int i = 0; i < n; i++)
    {
        cout << c[i]<<endl;
    }
}

常见错误

表达式必须是可修改的左值

c++
1.num[10]是字符数组名,a[i].num指向字符常量,字符数组名是无法修改的 .
2.所以可以用strcpy字符串复制功能就可以了.
3.另外写成s=a[i];也可以.
4.又或者将结构体内char num[10]改成string num.

“数组名是数组第一个元素的地址,代表指针型常量”。注意指针常量和常量指针的区别,*和const谁先在前就读谁。

charr1与charr2类型为char* const

搜索了百度百科得到:指针常量的值是指针,这个值因为是常量,所以不能被赋值。

为什么我们不能在switch中定义一个变量两次?

遵循这些事实:->You不能在一个作用域中两次声明同一个局部变量。->case不会创建自己的作用域。->switch,是的,创建自己的作用域。->Proof:如果您不break一个用例,那么所有的用例都会被执行,除非满足条件。(不像else if)。

动态类数组

c++
#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
#include <stdlib.h>
#include <iomanip>
using namespace std;
class Student
{
    public:
        void  Input();    //用于输入学生姓名,学号和三门课程成绩
        int Sum();   //用于计算3个学科的总分
        void Display();    //用于输出学生姓名,学号和三门课程成绩
        double Averate();  //用于计算3个学科的平均分
    private:
        string name;   //学生姓名
        int rollno;     //学生学号
        int sub_marks[3];   //3门课程成绩
};
void Student::Input(){
    cin>>name>>rollno>>sub_marks[0]>>sub_marks[1]>>sub_marks[2];
}
int Student::Sum(){
    return sub_marks[0]+sub_marks[1]+sub_marks[2];
}
double Student::Averate(){
    return Sum()/3.0;
}
void Student::Display(){
    cout<<"姓名:"<<name<<" 学号:"<<rollno<<" 平均分:"<< fixed << setprecision(2)<<Averate()<<" 总分:"<<Sum()<<endl;
}
int main()
{
    int n;
    cin>>n;
    Student *stu;
    stu = new Student[n]; // 动态多对象
    //或者 Student *stu = new Student[n];
    for(int i=0;i<n;i++)
    {
        stu[i].Input();
    }
    Student temp[1];
        for(int i=0;i<n;i++)
    {
        for(int loop=i+1;loop<n;loop++)
        {
            if(stu[loop].Sum()<stu[loop+1].Sum())
            {
                temp[0]=stu[loop+1];
                stu[loop+1]=stu[loop];
                stu[loop]=temp[0];
            }
        }
    }
    for(int i=0;i<n;i++)
    {
        stu[i].Display();
    }
    return 0;
}

string类型是字符串

char是一个字符

二维动态数组实例测试:

c++
#include <iostream>
using namespace std;
 
int main()
{
    int **p;   
    int i,j;   //p[4][8]  i,j可以赋值
    //开始分配4行8列的二维数据   
    p = new int *[4];
    for(i=0;i<4;i++){
        p[i]=new int [8];
    }
 
    for(i=0; i<4; i++){
        for(j=0; j<8; j++){
            p[i][j] = j*i; //赋值
        }
    }   
    //打印数据   
    for(i=0; i<4; i++){
        for(j=0; j<8; j++)     
        {   
            if(j==0) cout<<endl;   
            cout<<p[i][j]<<"\t";   
        }
    }   
    //开始释放申请的堆   
    for(i=0; i<4; i++){
        delete [] p[i];   
    }
    delete [] p;   
    return 0;
}

(int)*char的时候是按ascii计算的

C++定义动态数组

首先:为什么需要动态定义数组呢? 这是因为,很多情况下,在预编译过程阶段,数组的长度是不能预先知道的,必须在程序运行时动态的给出 但是问题是,c++要求定义数组时,必须明确给定数组的大小,要不然编译通不过

如:

c++
int Array[5];  正确
int i=5;
int Array[i]; 错误 因为在编译阶段,编译器并不知道 i 的值是多少

那么,我们该如何解决定义长度未知的数组呢? 答案是:new 动态定义数组

但动态数组的长度sizeof始终是4

因为new 就是用来动态开辟空间的,所以当然可以用来开辟一个数组空间

这样,下面的语句:

c++
  int size=50;
  int *p=new int[size]; 
  是正确的

在类中定义动态数组

c++
#include <iostream>
#include <string>
using namespace std;
//template <typename T1>

class Array
{
public:
    Array(int n) { a = new int[n]; }
public:
    int *a;
};
int main()
{
    int n;
    cin >> n;
    Array a(n);
    a.a[0] = 1;
    a.a[1] = 2;
    a.a[2] = 3;
    a.a[3] = 4;
    cout<<a.a[0]<<endl;
    cout<<a.a[1]<<endl;
    cout<<a.a[2]<<endl;
    cout<<a.a[3]<<endl;

    return 0;
}

动态数组无法被引用来传递参数

首先,可以明确的说明,引用的数组是不能当函数的参数的。再者要说明,这种方式是非法的

各个维数不固定的二维数组

如果二维数组的各个维数不固定,我们将不能使用以上定义方法,可以通过以下2种简单的方法实现。

  1. 将二维数组当一维数组操作

被调用函数定义:

c++
void f(int *a,int n); 1

实参传递:

c++
int a[3][4];  
f(*a,12);  12
  1. 手工转变寻址方式

被调用函数定义:

c++
void f(int **a,int m,int n);  1

实参传递:

c++
int a[3][4];  
f((int **)a,3,4);  12

这样在被调用数组中对对元素a[i][j]的访问可以使用如下形式:

c++
*((int *)a+n*i+j);  1

注意不能使用a[i][j]来直接访问,因为编译器无法为其定位。

布尔类型运算符重载

友元函数能用两个参数值,但成员函数只有一个

c++
    friend bool operator<(Rational &R1, Rational &R2)
    {
        if (R1.iUp * R2.iDown < R2.iUp * R1.iDown)
        {
            return true;
        }
        else
        {
            return false;
        }
    }


   if (R1 < R2)
    {
        cout << "a<b:true" << endl;
    }
    else
    {
        cout << "a<b:false" << endl;
    }

image-20220425112952905

c++

#include <iostream>
using namespace std;

class Rational
{
private:
    int iUp, iDown;

private:
    void Reduce();
    int Gcd(int, int);

public:
    int gcd, iUpgcd, iDowngcd, numInt;
    bool fra;
    int operator-();
    Rational(int, int);
    friend istream &operator>>(istream &input, Rational &R)
    {
        input >> R.iUp >> R.iDown;
        R.Gcd(R.iUp, R.iDown);
        R.Reduce();
        return input;
    }
    friend ostream &operator<<(ostream &output, Rational &R)
    {
        if (R.fra != 0)
        {
            output << R.iUpgcd << "/" << R.iDowngcd;
        }
        else
        {
            output << R.numInt;
        }
        return output;
    }
    friend Rational operator+(Rational &R1, Rational &R2)
    {

        int Up = R1.iUp * R2.iDown + R2.iUp * R1.iDown;
        int Down = R1.iDown * R2.iDown;
        Rational R3(Up, Down);
        R3.Gcd(Up, Down);
        R3.Reduce();
        return R3;
    }
    friend Rational operator*(Rational &R1, Rational &R2)
    {
        int Up = R1.iUp * R2.iUp;
        int Down = R1.iDown * R2.iDown;
        Rational R3(Up, Down);
        R3.Gcd(Up, Down);
        R3.Reduce();
        return R3;
    }
    friend Rational operator/(Rational &R1, Rational &R2)
    {
        int Up = R1.iUp * R2.iDown;
        int Down = R1.iDown * R2.iUp;
        Rational R3(Up, Down);
        R3.Gcd(Up, Down);
        R3.Reduce();
        return R3;
    }
    friend Rational operator-(Rational &R1, Rational &R2)
    {
        int Up = R1.iUp * R2.iDown - R2.iUp * R1.iDown;
        int Down = R1.iDown * R2.iDown;
        Rational R3(Up, Down);
        if (Up < 0)
        {
            R3.iUpgcd = Up;
            R3.iDowngcd = Down;
            R3.fra = 1;
        }
        else
        {
            if (Up == 0)
            {
                R3.iUpgcd = Up;
                R3.iDowngcd = Down;
                R3.fra = 0;
            }
            else
            {
                R3.Gcd(Up, Down);
                R3.Reduce();
            }
        }

        return R3;
    }
    friend bool operator<(Rational &R1, Rational &R2)
    {
        if (R1.iUp * R2.iDown < R2.iUp * R1.iDown)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    friend bool operator>(Rational &R1, Rational &R2)
    {
        if (R1.iUp * R2.iDown > R2.iUp * R1.iDown)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    friend bool operator<=(Rational &R1, Rational &R2)
    {
        if (R1.iUp * R2.iDown <= R2.iUp * R1.iDown)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    friend bool operator>=(Rational &R1, Rational &R2)
    {
        if (R1.iUp * R2.iDown >= R2.iUp * R1.iDown)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
};
int Rational::operator-()
{
    if (this->fra == 0)
    {
        return this->numInt * -1;
    }
    else
    {
        return this->iUpgcd * -1;
    }
}

Rational::Rational(int U = 0, int D = 0) : iUp(U), iDown(D) {}

void Rational::Reduce()
{
    if ((this->iUp % this->iDown) == 0)
    {
        this->fra = false;
        this->numInt = this->iUp / this->iDown;
    }
    else
    {
        iUpgcd = this->iUp / this->gcd;
        iDowngcd = this->iDown / this->gcd;
        this->fra = true;
    }
}

int Rational::Gcd(int l, int r) // l=0,r=1
{

    while (l != r)
    {
        if (l > r)
            l -= r;
        else
            r -= l;
    }
    this->gcd = l;
    return gcd;
}

int main()
{
    Rational R1, R2, R3;
    cin >> R1 >> R2;
    if (R1.fra == 0)
    {
        cout << "-a:" << -R1 << endl;
    }
    else
    {
        cout << "-a:" << -R1.iUpgcd << "/" << R1.iDowngcd << endl;
    }
    if (R2.fra == 0)
    {
        cout << "-b:" << -R2 << endl;
    }
    else
    {
        cout << "-b:" << -R2.iUpgcd << "/" << R2.iDowngcd << endl;
    }
    R3 = R1 + R2;
    cout << "a+b:" << R3 << endl;
    R3 = R1 - R2;
    cout << "a-b:" << R3 << endl;
    R3 = R1 * R2;
    cout << "a*b:" << R3 << endl;
    R3 = R1 / R2;
    cout << "a/b:" << R3 << endl;
    if (R1 < R2)
    {
        cout << "a<b:true" << endl;
    }
    else
    {
        cout << "a<b:false" << endl;
    }
    if (R1 <= R2)
    {
        cout << "a<=b:true" << endl;
    }
    else
    {
        cout << "a<=b:false" << endl;
    }
    if (R1 > R2)
    {
        cout << "a>b:true" << endl;
    }
    else
    {
        cout << "a>b:false" << endl;
    }
    if (R1 >= R2)
    {
        cout << "a>=b:true" << endl;
    }
    else
    {
        cout << "a>=b:false" << endl;
    }
    return 0;
}

动态链表

c++
Time::Time(int hour = 0, int minute = 0) : hour(hour), minute(minute) {}

C++ 格式化输出

1. 默认情况

我们先看C++默认的浮点数输出。

cpp
#include<iostream>
using namespace std;
int main(){
	cout << 0.123456789 << endl;//0.123457
	cout << 3.123456789 << endl;//3.12346
	cout << 33.23456789 << endl;//33.2346
} 
1234567

可以看出来,默认情况下浮点类型的输出有效位数为6,且会自动四舍五入

2. 前置补0

情景:计算某个时间,我们需要格式化输出时间HH:mm:ss。倘若时间不够10,则需要在前面补0。 小声BB:可能C++也有类似Java的SimpleDateFormat,but who care?

知识点:

  • 头文件iomanip

  • 输出控制符

    setw
    • 理解:set width设置输出宽度。
    • 作用范围:仅对后续的<<生效一次。所以输出一次用一次。
  • 输出控制符

    setfill
    • 理解:set fill character 设置填充的字符,不写参数的话默认是空格。
    • 作用范围:自设置之后的所有<<,直到下一个setfill才更改。所以用一次就好啦。

代码:

cpp
#include<iostream>
#include<iomanip>
using namespace std;

int main(){
	int hour = 5;
	int minute = 30;
	int second = 0;	
	cout << setw(2) << setfill('0') << hour <<":" 
		 << setw(2) << minute <<":"
		 << setw(2) << second << endl;
} 
123456789101112

这样就可以输出05:30:00的格式了。

此处的代码比较简单,需要额外注意,若setw(n)后接浮点数,则小数点也算一个宽度。若setw(n)后接的数值宽度大于n,则会全部输出。

cpp
float six = 123456;
float three = 12.3;
float mini = 0.123123;
cout << setw(4) << setfill('*') << six <<endl 
	 << setw(4) << three <<endl
	 << setw(4) << mini << endl;
/*输出如下 
	123456
	12.3
	0.1234
*/
1234567891011

3. 保留有效位数

  • 头文件iomanip

  • 输出控制符

    setprecision
    • 理解:可以设置输出精度。(总有效位数,包括整数部分)
    • 作用范围:同setfill,可以对后续的输出产生影响。

为了对比原本的输出和设置精度后的输出,后续的几个测试示例都是分开测试的。

cpp
float big = 12345;
cout << big <<" after " << setprecision(4) << big << endl;
//12345 after 1.234e+04
123
float middle = 1.2345;	
cout << middle <<" after " << setprecision(4) << middle << endl;
//1.2345 after 1.235
123
float mini = 0.12345;
cout << mini <<" after " << setprecision(4) << mini << endl;
//0.12345 after 0.1235
123

4. 保留小数

  • 头文件iomanip

  • 流操作符

    fixed
    • 理解:它表示浮点输出应该以固定点或小数点表示法显示。
    • 作用范围:之后所有。

fixed 操作符可能最重要的还是当它与 setprecision 操作符一起使用时,setprecision 即可以以一种新的方式显示。它将指定浮点数字的小数点后要显示的位数,而不是要显示的总有效数位数。而这通常正是我们想要的。 ——摘自C语言中文网

代码如下:由于作用范围包括后面的,所以我们可以提前就设置好,后续输出即可。

cpp
cout << fixed << setprecision(4);	
float big = 12345;
cout  << big << endl;// 12345.0000
float middle = 1.2345;	
cout << middle << endl;// 1.2345
float mini = 0.12345;
cout << mini << endl;//0.1235
1234567

可以看出来,不管数值大小,最终都能以四舍五入保留4位小数。

5. 向下取整,向上取整,四舍五入

引入cmath头文件,利用ceil,floor,round函数可以实现该整型功能。

Tip:

  1. 若是需要保留2位小数时取整,则可以通过乘100,再取整,最后除100的方式实现。
  2. 通过强制类型转化可以实现向下取整。
cpp
float a = 12.3456;
//基本函数调用
cout<<ceil(a)<<endl;   //向上取整
cout<<floor(a)<<endl;   //向下取整
cout<<round(a)<<endl;   //四舍五入
//不使用函数实现
//向下取整
cout<<(int)a<<endl;
//向上取整
cout<<(a>(int)a?(int)a+1:(int)a)<<endl;
//四舍五入
cout<<(int)(a+0.5)<<endl;

c++的const和static区别

static是静态 const是常量

const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间。

static表示的是静态的。类的静态成员函数、静态成员变量是和类相关的,而不是和类的具体对象相关的。即使没有具体对象,也能调用类的静态成员函数和成员变量。一般类的静态函数几乎就是一个全局函数,只不过它的作用域限于包含它的文件中。

在C++中,static静态成员变量不能在类的内部初始化。在类的内部只是声明定义必须在类定义体的外部,通常在类的实现文件中初始化,如:

c++
double Account::Rate=2.25;

static关键字只能用于类定义体内部的声明中,定义时不能标示为static

在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。

const数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。

const数据成员的初始化只能在类的构造函数的初始化列表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static cosnt。

静态的意思是跟这个类的对象实例没关系,是类范围的,一个类可以生成多个对象,但只有一份静态数据成员。 普通函数成员会隐式传递一个this指针,指向调用这个函数的对象;而静态函数成员不包括这个指针,所以在 静态成员 函数内不能使用非静态的数据成员和函数。

类的析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。

析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

下面的实例有助于更好地理解析构函数的概念:

c++
#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();   // 这是构造函数声明
      ~Line();  // 这是析构函数声明
 
   private:
      double length;
};
 
// 成员函数定义,包括构造函数
Line::Line(void)
{
    cout << "Object is being created" << endl;
}
Line::~Line(void)
{
    cout << "Object is being deleted" << endl;
}
 
void Line::setLength( double len )
{
    length = len;
}
 
double Line::getLength( void )
{
    return length;
}
// 程序的主函数
int main( )
{
   Line line;
 
   // 设置长度
   line.setLength(6.0); 
   cout << "Length of line : " << line.getLength() <<endl;
 
   return 0;
}

继承后的构造函数和析构函数顺序

c++
#include<iostream>
using namespace std;

class A
{
private:
    int x;
public:  
    A(int a=0); 
    ~A();
};
A::A(int a)
{
    x=a;
    cout<<x<<endl;
    cout<<"Class A Con."<<endl;
}
//补充代码,完成类A的析构函数
A::~A()
{
    cout<<"Class A DeCon."<<endl;
}
class B
{
private:  
    int y;
public:  
    B(int a=0);
    ~B();
};
B::B(int a)
{
    y=a;
    cout<<y<<endl;
    cout<<"Class B Con."<<endl;
}
//补充代码,完成类B的析构函数
B::~B()
{
    cout<<"Class B DeCon."<<endl;
}

//补充代码,使类C公有继承自类A和类B 
class C:public B,public A
{
private: 
    int z;
public:
    C(int a=0,int b=0); 
    ~C();
};
C::C(int a,int b):A(a),B(a+20)
{
    z=b;
    cout<<z<<endl;
    cout<<"Class C Con."<<endl;
}
//补充代码,完成类C的析构函数
C::~C()
{
    cout<<"Class C DeCon."<<endl;
}
int main( )
{ 
    C c(100,200);
    return 0;
}

output
120
Class B Con.
100
Class A Con.
200
Class C Con.
Class C DeCon.
Class A DeCon.
Class B DeCon.

如同穿衣服一样先穿B,然后穿A

class C:public B,public A

脱衣服是先脱A再脱B,继承的构造函数是从左到右 B->A

构造函数能否被继承

  1. 构造函数不能为 virtual, 构造函数不能继承;

  2. 如果子类不显式调用父类的构造函数,编译器会自动调用父类的【无参构造函数】;

  3. 继承构造函数(Inheriting constructors)

(1) C++11 才支持;

(2) 实质是编译器自动生成代码,通过调用父类构造函数来实现,不是真正意义上的【继承】,仅仅是为了减少代码书写量(参考 《C++ Primer》)。

使用初始化列表来初始化字段

image-20220430203615961

注意,只能在构造函数里面使用,而且前面必须是本类所有的数据成员

否则报错

image-20220430203709811

对于不是本来的数据成员,可以调用父类里面的构造函数

image-20220430203822553

或者使用 this->

image-20220430203859000

虚基类

多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个。

多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示:

img 图1:菱形继承

类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径。

在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义,编译器不知道它究竟来自 A -->B-->D 这条路径,还是来自 A-->C-->D 这条路径。

虚继承(Virtual Inheritance)

为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。

在继承方式前面加上 virtual 关键字就是虚继承,请看下面的例子:

c++
//间接基类A
class A{
protected:
    int m_a;
};

//直接基类B
class B: virtual public A{  //虚继承
protected:
    int m_b;
};

//直接基类C
class C: virtual public A{  //虚继承
protected:
    int m_c;
};

//派生类D
class D: public B, public C{
public:
    void seta(int a){ m_a = a; }  //正确
    void setb(int b){ m_b = b; }  //正确
    void setc(int c){ m_c = c; }  //正确
    void setd(int d){ m_d = d; }  //正确
private:
    int m_d;
};

int main(){
    D d;
    return 0;
}

这段代码使用虚继承重新实现了上图所示的菱形继承,这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了。

虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

现在让我们重新梳理一下本例的继承关系,如下图所示:

img 图2:使用虚继承解决菱形继承中的命名冲突问题

观察这个新的继承体系,我们会发现虚继承的一个不太直观的特征:必须在虚派生的真实需求出现前就已经完成虚派生的操作。在上图中,当定义 D 类时才出现了对虚派生的需求,但是如果 B 类和 C 类不是从 A 类虚派生得到的,那么 D 类还是会保留 A 类的两份成员。

换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。

在实际开发中,位于中间层次的基类将其继承声明为虚继承一般不会带来什么问题。通常情况下,使用虚继承的类层次是由一个人或者一个项目组一次性设计完成的。对于一个独立开发的类来说,很少需要基类中的某一个类是虚基类,况且新类的开发者也无法改变已经存在的类体系。

c++调用不同类中的方法

c++
student S
S.teacher::say();

C++ 虚函数和纯虚函数的区别

定义一个函数为虚函数,不代表函数为不被实现的函数。

(定义为虚函数后,无法实例化对象,就算有其他正常函数也不行)

定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。

定义一个函数为纯虚函数,才代表函数没有被实现。

定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

虚函数

虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。

我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定

构造函数不能声明为虚函数,析构函数一般声明为虚函数

必须和基类的参数数量,类型,返回值类型 一样 (如果不一样那就属于重写了)

但重写的话无所谓,这就是虚函数和重写的区别

  1. 重载一个虚函数时,要求函数名、返回类型、参数个数、参数类型以及参数的顺序都必
  2. 须与基类中的虚函数原型完全相同如果仅仅是返回类型不同其余均相同,系统会给出错误信息。
  3. 若仅仅是函数名相同,而参数的个数、类型或顺序不同,则系统将它作为普通的函数重载,这时将丢失虚函数的特性。

如果没有虚函数的声明,然后使用虚函数的方式,那它声明什么类型的变量那就是谁的

c++
D d;
B *p= &d; // 为B类中的方法
p->f();

如果不加关键字virtual

c++
Shape *shape;
shape = &rec;
// 调用矩形的求面积函数 area
shape->area(); 
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
 shape->area();
 
 
 当上面的代码被编译和执行时,它会产生下列结果:
Parent class area :
Parent class area :

加上关键字后就会根据定义的指针类型来调用相应的函数

c++
但现在,让我们对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual,如下所示:

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual int area()
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};
修改后,当编译和执行前面的实例代码时,它会产生以下结果:

Rectangle class area :
Triangle class area :

C++规定,基类的对象指针可以指向它的公有派生类的对象,但是当其指向公有派生类对象时,它只能访问派生类中从基类继承来的成员(没被重写前的),而不能访问公有派生类中定义的成员。(被重写后的)

使用虚函数的四种方法

定义一个公共的类型,然后让各个引用或赋值给他地址,然后用公共的来调用函数

c++
#include <iostream>
using namespace std;
class A
{
private:
    int x;

public:
    A(int a = 0)
    {
        this->x = a;
    }
    virtual void show()
    {
        cout << "This A" << endl;
    }
};
class B : public A
{
private:
    int b;

public:
    B(int a = 0)
    {
        this->b = a;
    }
    void  show()
    {
        cout << "This B" << endl;
    }
};
int main()
{
    B b;
    A &a = b;
    a.show();
    // 使用引用的方法,这里因为a不是地址可用.调用方法


    A *a = new B();
    a->show();
    
    A *a;
    B b;
    a = &b;
    a->show();
	// 上面是简略版,使用地址调用方法
    B b;
    b.show();
    //直接重写
    Monster *p[2]={new wukong,new Bajie}
    return 0;
}

纯虚函数

c++
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。

我们可以把基类中的虚函数 area() 改写如下:

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      // pure virtual function
      virtual int area() = 0;
};

= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。

包含一个或多个纯虚函数的类称为抽象类

类模板

使用类模板创建对象

上面的两段代码完成了类的定义,接下来就可以使用该类创建对象了。使用类模板创建对象时,需要指明具体的数据类型。请看下面的代码:

c++
Point<int, int> p1(10, 20);
Point<int, float> p2(10, 15.5);
Point<float, char*> p3(12.4, "东经180度");

与函数模板不同的是,类模板在实例化时必须显式地指明数据类型,编译器不能根据给定的数据推演出数据类型。

原来你在类外写函数实现的时候,你需要重新声明模板类。虽然听起来很扯,但是确实需要这样😂 所以为了方便,可以在类中直接写完函数实现,不用再写到类外了。

c++
template <typename T>
class Complex{
public:
    T real;
    T imag;
public:
   T Add(Complex c1);
};
template <typename T>
T Complex<T>::Add(Complex c1)
    //或者
template <typename T>
class Complex{
public:
    T real;
    T imag;
public:
   Complex<T> Add(Complex c1);
};
template <typename T>
Complex<T> Complex<T>::Add(Complex c1)

类模板的成员函数都是在类的内部实现的,类模板的成员函数可以在类模板的定义中定义(inline函数),也可以在类模板定义之外定义(此时成员函数定义前面必须加上template及模板参数),在类模板外部定义成员函数的方法如下所示:

c++
template<模板形参表>
函数返回类型 类名<模板形参名>::函数名(参数列表){}

综合示例

将上面的类定义和类实例化的代码整合起来,构成一个完整的示例,如下所示:

c++
#include <iostream>
using namespace std;

template<class T1, class T2>  //这里不能有分号
class Point{
public:
    Point(T1 x, T2 y): m_x(x), m_y(y){ }
public:
    T1 getX() const;  //获取x坐标
    void setX(T1 x);  //设置x坐标
    T2 getY() const;  //获取y坐标
    void setY(T2 y);  //设置y坐标
private:
    T1 m_x;  //x坐标
    T2 m_y;  //y坐标
};

template<class T1, class T2>  //模板头
T1 Point<T1, T2>::getX() const /*函数头*/ {
    return m_x;
}

template<class T1, class T2>
void Point<T1, T2>::setX(T1 x){
    m_x = x;
}

template<class T1, class T2>
T2 Point<T1, T2>::getY() const{
    return m_y;
}

template<class T1, class T2>
void Point<T1, T2>::setY(T2 y){
    m_y = y;
}

int main(){
    Point<int, int> p1(10, 20);
    cout<<"x="<<p1.getX()<<", y="<<p1.getY()<<endl;
 
    Point<int, char*> p2(10, "东经180度");
    cout<<"x="<<p2.getX()<<", y="<<p2.getY()<<endl;
 
    Point<char*, char*> *p3 = new Point<char*, char*>("东经180度", "北纬210度");
    cout<<"x="<<p3->getX()<<", y="<<p3->getY()<<endl;

    return 0;
}

运行结果: x=10, y=20 x=10, y=东经180度 x=东经180度, y=北纬210度

在定义类型参数时我们使用了 class,而不是 typename,这样做的目的是让读者对两种写法都熟悉。

求数组长度

对于第一种求数组长度的办法,可能不是很明白

c++
为什么需要sizeof()之后,需要除以sizeof(arr[0])。这里有几点需要注意:

1、sizeof不是函数,是操作符,它是编译时求一个类型所占的字节数。
2sizeof(arr):以长度4的int数组为例,其实求出了int类型数组所占总长度4*4=16。然后需要按照任意一个类型长度其实就是4来做除法。
    
    strlen(arr)//取字符串长度

结合动态数组的替换数组方案(修改原数组长度)

c++
#include <iostream>
#include <string>
using namespace std;
template <typename T1>

class Array
{
public:
    T1 *arry;
    int ArrLen;

public:
    Array(int n)
    {
        arry = new T1[n];
        this->ArrLen = n;
    }
    void show()
    {
        if (ArrLen == 0)
        {
            cout << "Array is empty" << endl;
        }
        else
        {
            for (int i = 0; i < ArrLen; i++)
            {
                cout << arry[i] << " ";
            }
            cout << endl;
        }
    }
    T1 get(int i)
    {
        return arry[i - 1];
    }
    void insert(int x, T1 y)
    {
        if (x - ArrLen > 1 | x==0)
        {
            cout << "insert fail" << endl;
        }
        else
        {
            this->ArrLen++;
            T1 *temp = new T1[this->ArrLen];
            for (int i = 0; i < x - 1; i++)
            {
                temp[i] = arry[i];
            }
                temp[x - 1] = y;
            
            for (int i = x; i < this->ArrLen; i++)
            {
                temp[i] = arry[i - 1];
            }
            delete[] arry;  //通过删除之前的动态数组来替换掉旧数组
            arry = temp;	//l
            cout << "insert OK" << endl;
        }
    };
    void del(int x)
    {
        if (x > ArrLen | x==0)
        {
            cout << "delete fail" << endl;
        }
        else
        {
            this->ArrLen--;
            T1 *temp = new T1[this->ArrLen];
            for (int i = 0; i < x - 1; i++)
            {
                temp[i] = arry[i];
            }
            for (int i = x; i < this->ArrLen+1; i++)
            {
                temp[i-1] = arry[i];
            }
            delete[] arry;
            arry = temp;
            cout << "delete OK" << endl;
        }
    };
};
int main()
{
    int n, len, x, y;
    string str;
    cin >> n;
    Array<int> a(n);
    for (int i = 0; i < n; i++)
    {
        cin >> a.arry[i];
    }
    cin >> len;
    while (len--)
    {
        cin >> str;
        if (str == "show")
            a.show();
        if (str == "get")
        {
            cin >> x;
            if (x > 0 && x <= a.ArrLen)
            {
                cout << a.get(x) << endl;
            }
            else
            {
                cout << "get fail" << endl;
            }
        }
        if (str == "insert")
        {
            cin >> x >> y;
            a.insert(x, y);
        }
        if (str == "delete")
        {
            cin >> x;
            a.del(x);
        }
    }
    // 上面是int 下面是double
    cin >> n;
    double z;
    Array<double> b(n);
    for (int i = 0; i < n; i++)
    {
        cin >> b.arry[i];
    }
    cin >> len;
    while (len--)
    {
        cin >> str;
        if (str == "show")
            b.show();
        if (str == "get")
        {
            cin >> x;
            if (x > 0 && x <= b.ArrLen)
            {
                cout << b.get(x) << endl;
            }
            else
            {
                cout << "get fail" << endl;
            }
        }
        if (str == "insert")
        {
            cin >> x >> z;
            b.insert(x, z);
        }
        if (str == "delete")
        {
            cin >> x;
            b.del(x);
        }
    }

    return 0;
}

自定义头文件

login.cpp

c++
#include "login.h"


void login()       //输入密码进入系统,欢迎界面
{
    string InputPassword;
    string Password = { "admin" };  //设定密码

inputpassword: printf(" \n\n\n\n\n\n\n\n");
    printf("\t\t\t欢迎使用学生成绩管理系统!\n\n");
    printf("\t\t\t请输入密码(admin):");
    cin >> InputPassword;
    if (InputPassword == Password)
    {
        system("cls");
        printf(" \n\n\n\n\n\n\n\n\n\t\t\t密码正确,正在进入!");
        Sleep(1000);
    }
    else
    {
        printf("\t\t\t\t 密码错误,请重新输入!");           //密码错误重新输出
        Sleep(2000);
        system("cls");
        goto inputpassword;
    }
}

login.h

c++
#pragma once
#include "head.h"


void login();

head.h

c++
#pragma once
// include 统一管理
#include <iostream>
#include <string>
#include <cstring>
#include <fstream>
#include <windows.h>
#include <iomanip>
#include <string.h>
using namespace std;            //命名空间 std

main.cpp

c++
#include "head.h"  //头文件
#include "login.h" //登录页面

不同文件之间数据传递

  1. 如果在头文件中定义了全局变量的话,会因为多次调用头文件而造成重复声明的错误。(只调用一次的话没问题)

  2. 头文件重复包含的话可以通过解决,但如果有全局变量的话仍然会报错。

    c++
    #pragma once
    #ifndef _data_
    #define _data_
    #endif
  3. exten方法

    c++
    第一个文件:main.c
    
    #include <stdio.h>
    int count ; //声明
    extern void write_extern(); 声明
    int main()
    {
       count = 5; //定义
       write_extern();  //使用
    }
    第二个文件:support.c
    #include <stdio.h>
     
    extern int count; //声明
     
    void write_extern(void) //定义
    {
       printf("count is %d\n", count); //使用
    }

    简单来说就是在一个文件中 声明或定义后,在另一个文件加上 extern就可以在其中使用了,坏处就是要文件多了的话,每一个都要写一遍extern,所以可以写在头文件中,分别调用。(另建立一个数据h,别写在主文件里,会报冲突)

    如果不加extern,分别写入文件的话也会报错。

    与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用

    extern是C/C++语言中表明函数和全局变量**作用范围**(可见性)的关键字,它告诉编译器,其**声明**的函数和变量可以在本模块或其它模块中使用。 1.对于extern变量来说,仅仅是一个变量的声明,其并不是在定义分配内存空间。如果该变量定义多次,会有连接错误. 2.通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。也就是说c文件里面定义,如果该函数或者变量与开放给外面,则在h文件中用extern加以声明。所以外部文件只用include该h文件就可以了。而且编译阶段,外面是找不到该函数的,但是不报错。link阶段会从定义模块生成的目标代码中找到此函数。

  4. extern "C"

    在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern"C"时会出现编译语法错误。

    在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理

    c++
    extern "C" 
    { 
        #include "cExample.h" 
    }

    如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern"C"

    在C中引用C++语言中的函数和变量时,C++的头文件需添加extern"C",但是在C语言中不能直接引用声明了extern"C"的该头文件,应该仅将C文件中将C++中定义的extern"C"函数声明为extern"C"类型

    来源

Released under the MIT License.