记录c++的学习

记录c++的学习

(仅供参考,欢迎大家交流学习)
2021.1.1

引用

1、引用的定义

1.引用基本语法:数据类型 &别名 = 原名
(引用必须要初始化,即 数据类型 &别名 = 原名)
2.引用一旦初始化,就不能更改
下面展示一些 代码

    int a=10;
    int &b=a;//引用必须初始化
    cout<<"a= "<<a<<endl;
	cout<<"b= "<<b<<endl;
	cout<<endl;
	b=99;
	cout<<"a= "<<a<<endl;
	cout<<"b= "<<b<<endl;
	cout<<endl;
	int c=35;
	b=c;//赋值操作,不是更改引用。
	cout<<"a= "<<a<<endl;
	cout<<"b= "<<b<<endl;
	cout<<"c= "<<c<<endl; 

代码运行结果:
在这里插入图片描述

2、引用做函数参数:

2.1 值传递:

下面展示一些 代码

#include <iostream>
using namespace std;
void mySwap01(int a,int b);
int main()
{
    
    
    int a=10;
    int b=20;
    mySwap01(a,b);//值传递,形参不会修饰实参 
   	cout<<"a= "<<a<<endl;
	cout<<"b= "<<b<<endl;
	
	return 0; 
}
void mySwap01(int a,int b)//值传递 
{
    
    
	//形参改变了,但实参没有发生改变 
	int temp;
	temp=a;
	a=b;
	b=temp;
	cout<<"swap a= "<<a<<endl;
	cout<<"swap b= "<<b<<endl;
}

代码运行结果:
(形参发生改变,但实参并没有发生改变)
在这里插入图片描述

2.2 地址传递:

下面展示一些 代码

#include <iostream>
using namespace std;
void mySwap02(int *a,int *b);
int main()
{
    
    
    int a=10;
    int b=20;
    mySwap02(&a,&b);//地址传递,形参会修饰实参 
   	cout<<"a= "<<a<<endl;
	cout<<"b= "<<b<<endl;
	
	return 0; 
}
void mySwap02(int *a,int *b)//用指针接收地址 
{
    
     
    /*mySwap02(int *a,int *b)用指针接收地址, 
    int *a是指针,&a是地址
	mySwap02(&a,&b)*/
	int temp;
	temp=*a;
	*a=*b;
	*b=temp;
	//因为是指针,所以输出的是地址 
	cout<<"swap a= "<<a<<endl; 
	cout<<"swap b= "<<b<<endl;
}


代码运行结果:
(地址传递,形参会修饰实参 )
在这里插入图片描述

2.3 引用传递:

下面展示一些 代码

#include <iostream>
using namespace std;
void mySwap03(int &a,int &b);
int main()
{
    
    
    int a=10;
    int b=20;
    mySwap03(a,b);//引用传递,形参会修饰实参 
   	cout<<"a= "<<a<<endl;
	cout<<"b= "<<b<<endl;
	return 0; 
}
void mySwap03(int &a,int &b)//引用传递 
{
    
    
	int temp;
	temp=a;
	a=b;
	b=temp;
	cout<<"swap a= "<<a<<endl;
	cout<<"swap b= "<<b<<endl;
}



代码运行结果:
(引用传递,形参会修饰实参 )
在这里插入图片描述
2021.1.1

函数

1、函数重载

1、函数重载满足的条件:
(1)同一个作用域下。
(2)函数名称相同。
(3)函数参数类型不同,或者个数不同,或者顺序不同。
注意事项:函数的返回值不能作为函数重载的条件。
下面展示一些 代码

    #include <iostream>
using namespace std;
void func();
void func(int a,int b);
void func(double a,int b);
void func(int a,double b);
//函数参数类型不同,个数不同,顺序不同。即为函数重载。
int main()
{
    
    
    func();
    func(1,2);
    func(1.5,2);
    func(1,1.5);
	return 0; 
}
void func()
{
    
    
	cout<<"func()的调用"<<endl; 
}
void func(int a,int b)
{
    
    
	cout<<"func(int a,int b)的调用"<<endl; 
}
void func(double a,int b)
{
    
    
	cout<<"func(double a,int b)的调用"<<endl; 
}
void func(int a,double b)
{
    
    
	cout<<"func(int a,double b)的调用"<<endl; 
}

代码运行结果:
在这里插入图片描述
2021.1.1

类和对象

c++面对对象的三大特性为:封装,继承,多态

1、封装

1、语法:class 类名{ 访问权限: 属性 / 行为};
2、属性通常是一些变量,行为通常是函数。
3、通过这个类,实例化出一个圆circle(对象),即通过一个类,
创建一个对象。
4、用“.”来调用属性和行为。
5、封装的意义一:在设计类的时候,将属性和行为写在一起,表现某个事物。
6、类中的属性和行为,我们统一称为成员。
属性:成员属性,成员变量
行为:成员函数,成员方法
下面展示一些 代码

#include <iostream>
using namespace std;
const double PI=3.14;
//设计一个圆类,求圆的周长
class circle 
{
    
    
	//访问权限
	public://(公共权限) 
	
	//属性(用一个变量) 
	double r;//半径 
	//行为(通常是一个函数) 
	double perimeter()//获取圆的周长 
	{
    
    
		return 2*PI*r;
	}
};
int main()
{
    
    
    /*有圆类了,但是没有具体的圆,
	所以我们要创建一个具体的圆.(即对象) */
	circle c1;//通过这个类,创建一个具体的对象
	//给圆对象 的属性进行赋值
	c1.r=2.5;//用"." 访问它的属性和行为
	cout<<"圆的周长为:"<<c1.perimeter()<<endl;
	//调用circle的行为。 
	
}



代码运行结果:
在这里插入图片描述

问题与思考:

自主设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号。
下面展示一些 代码

#include <iostream>
#include <string> 
using namespace std;
class student 
{
    
    
	//访问权限
	public://(公有权限) 
	
	//属性(用一个变量) 
	string name;//姓名用string类型 
	int number;//学号用int类型 
	//行为(通常是一个函数) 
	void information()//获取学生的姓名和学号 
	{
    
    
		cout<<"学生的姓名:"<<name<<endl;
		cout<<"学生的学号:"<<number<<endl;;
	}
};
int main()
{
    
    
    student s1;
	s1.name="刘备";
	s1.number=2018011;
	s1.information(); 
	
}




代码运行结果:
在这里插入图片描述
(这样做是可以得到我们想要的结果,但是我们还可以通过他的行为来给属性进行赋值)
这种方法是我们以后主要使用的方法:
下面展示一些 代码

#include <iostream>
#include <string> 
using namespace std;
class student 
{
    
    
	//访问权限
	public://(公有权限) 
	
	//属性(用一个变量) 
	string name1;//姓名用string类型 
	int number1;//学号用int类型 
	//行为(通常是一个函数) 
	//给姓名赋值: 
	void setName(string name)
	{
    
    
		name1=name;
	}
	//给学号赋值: 
	void setNumber(int number)
	{
    
    
		number1=number;
	}
	void information()//获取学生的姓名和学号 
	{
    
    
		cout<<"学生的姓名:"<<name1<<endl;
		cout<<"学生的学号:"<<number1<<endl;;
	}
};
int main()
{
    
    
    student s1;
	s1.setName("刘备");
	s1.setNumber(2018011);
	s1.information(); 
	
}




代码运行结果:
在这里插入图片描述

1.1 封装的权限控制:

一、三种访问权限
(1)公共权限public:
成员类内可以访问 类外可以访 问
(2)保护权限protected:
成员类内可以访问 类外不可以访问
儿子可以访问父亲中的保护内容(继承)
(3)私有权限private:
成员类内可以访问 类外不可以访问
儿子不可以访问父亲的私有内容(继承)
下面展示一些 代码

#include <iostream>
#include <string> 
using namespace std;
class Person 
{
    
    
	//姓名 公有权限
public:
	string name1; 
	
	//汽车 保护权限 
protected:
	string car1;
	
	//银行卡密码 私有权限
private: 
    int password1;
    
public: 
    void func()
    {
    
    
    	name1="张三";
    	car1="拖拉机";
    	password1=123456;
	}
};
int main()
{
    
    
    Person p;
	p.name1="李四";
	//p.car1="宝马" ; 保护权限内容,类外访问不到 
	//p.password1=555687; 私有权限内容,类外访问不到 
	 
	
}




1.2 struct 与 class区别:

1、在c++中struct与class唯一的区别就在于默认的访问权限不同。
2、struct 默认权限为公有public
3、class默认权限为私有private

#include <iostream>
#include <string> 
using namespace std;
class C1
{
    
    
	int a1;//默认私有private
 
};
struct C2
{
    
    
	int a2;//默认公有public 
};
int main()
{
    
    
    C1 m;
    //默认私有无法访问m.a1
	C2 m1;
	m1.a2=5
    
	 
	
}


1.3 成员属性设置为私有:

1、成员属性设置为私有:
(1)可以自己控制读写权限
(2)对于 写 可以检测数据的有效性

#include <iostream>
#include <string> 
using namespace std;
/*成员属性设置为私有:
1.可以自己控制读写权限
2.对于 写 可以检测数据的有效性 
*/ 
class Person
{
    
    
public: 
    //设置姓名 
    void setName(string name)
	{
    
    
	   name1=name;	
	} 
	//获取姓名
	string getName()//返回值为string类型,所以用string 
	{
    
    
	   return name1;	
    } 
    
    //获取年龄 
    int getAge()
    {
    
    
    	age1=20;//年龄初始化为20岁 
    	return age1;
	}
	
	void setLover(string lover)
	{
    
    
		lover1=lover;
	}
private: 
    //姓名  可读可写
    string name1;
	//年龄  只读
	int age1; 
	//女朋友  只写
	string lover1; 
	 
};

int main()
{
    
    
    Person p;
    p.setName("张三");
	cout<<"姓名为:"<<p.getName()<<endl; 
	cout<<"年龄为:"<<p.getAge()<<endl; 
	p.setLover("赵丽颖");

	
}


在这里插入图片描述

1.4 设计立方体类

要求:
(1)求出立方体的面积和体积。
(2)分别用全局函数和成员函数来判断两个立方体是否相等。

主要是明白成员函数以及全局函数的使用

#include <iostream>
#include <iomanip>
#include <stdlib.h>
using namespace std;
class  Cube
{
    
    
public:
	double L1,W1,H1,s,v;
	void setL(double L)
	{
    
    
		L1=L;		
	}
	int getL()
	{
    
    
		return L1;
	} 
	void setW(double W)
	{
    
    
		W1=W;		
	}
	int getW()
	{
    
    
		return W1;
	} 
	void setH(double H)
	{
    
    
		H1=H;		
	}
	int getH()
	{
    
    
		return H1;
	} 
	void getarea()
	{
    
    
		s=2*(L1*W1+L1*H1+W1*H1);
		cout<<"立方体的面积为:"<<s<<endl; 
	}
	void gettiji()
	{
    
    
		v=L1*H1*W1 ;
		cout<<"立方体的体积为:"<<v<<endl; 
	}
	//利用成员函数进行判断
	bool isSameByclass(Cube &c)
	{
    
    
		if(L1==c.getL()&&H1==c.getH()&&W1==c.getW())
		{
    
    
			return true;
		}
		else
		{
    
    
			return false;
		}
	} 
	
  	
};
//利用全局函数 判断两个立方体是否相等
bool isSame(Cube &c1,Cube &c2)//引用传递,直接使用原始的数据。 
{
    
    
   if(c1.getL()==c2.getL()&&c1.getH()==c2.getH()&&c1.getW()==c2.getW())//这里要用get返回值 
   {
    
    
   	return  true;
   }	
   else
   {
    
    
    return	false;
   }
} 
int main() 
{
    
    
    Cube p;
    p.setL(10);
    p.setW(10);
    p.setH(10);
    p.getarea();
    p.gettiji();
    //成员函数
	Cube p2;
	p2.setL(10);
    p2.setW(15);
    p2.setH(10);
	p2.getarea();
	p2.gettiji();
	bool ret=isSame(p,p2);//调用全局函数 
	if(ret==true)	{
    
    
		cout<<"c1和c2是相等的"<<endl ;
	}
	else
	{
    
    
		cout<<"c1和c2是不相等的"<<endl ;
	}
	ret=p.isSameByclass(p2);
	cout<<"成员函数:";
	if(ret)	{
    
    
		cout<<"c1和c2是相等的"<<endl ;
	}
	else
	{
    
    
		cout<<"c1和c2是不相等的"<<endl ;
	}

	return 0;
}

在这里插入图片描述

1.5求点与圆的关系

(1)运用到了两个类,如何调用两个类。
(2)运用到了多文件结构。

(1)不采用多文件结构:

#include<iostream>
using namespace std;
class Point
{
    
    
public:
	void setX(double x1)
	{
    
    
		x=x1;
	}
	double getX()
	{
    
    
		return x;
	}
	void setY(double y1)
	{
    
    
		y=y1;
	}
	double getY()
	{
    
    
		return y;
	}
private:
	double x;
	double y;
};
class Circle
{
    
    
public:
	void setR(double r1)
	{
    
    
		r=r1;
	}
	double getR()
	{
    
    
		return r;
	}
	//定义圆心,既然是点,就用Point类,然后返回类型也用Point类
	void setCenter(Point center1)
	{
    
    
		center=center1;
	}
	Point getCenter()
	{
    
    
		return center;
	}

private:
	double r;
	//要先定义了一个Point类,才能使用Point center
	Point center;
};
//判断圆心与点的距离:
void isCircle(Circle &c,Point &p)//最好用引用传递,虽然我也不清楚为什么
{
    
    
	double R=c.getR()*c.getR();//这里忘了加括号,找了很久的错误。
	double distance=(c.getCenter().getX()-p.getX())*(c.getCenter().getX()-p.getX())
		+(c.getCenter().getY()-p.getY())*(c.getCenter().getY()-p.getY());
	/*
	wdnmd,这个程序搞了一下午,一直不知道哪里有错误,因为VS不会自动填充括号,
	我一直找了很久的错误,醉了,以后一定要记得加括号
	*/
	if(distance==R)
	{
    
    
		cout<<"点在圆上"<<endl;
	}
	else if(distance>R)
	{
    
    
		cout<<"点在圆外"<<endl;
	}
	else
	{
    
    
		cout<<"点在圆内"<<endl;
	}


}
int main()
{
    
    
	//创建圆:
	Circle c;
	c.setR(5.0);
	Point center;
	center.setX(0.0);
	center.setY(0.0);
	//我们要先设置一个点,然后再创建圆心。给圆心赋值
	c.setCenter(center);//这一步很关键,因为要先用Point类创建一个点,再用哪个店来创建圆心。

	//创建点:
	Point p;
	p.setX(3.0);
	p.setY(4.0);
	isCircle(c,p);
	system("pause");
}

主要是理解,当我们遇到两个类时,我们应当如何调用。
在这里插入图片描述
(2)采用多文件结构:
先看代码:
头文件point.h:

//point.h
#pragma once//因为少加了pragma once,直接多了17个错误,我人傻了
class Point
{
    
    
public:
	void setX(double x1);
	
	double getX();
	
	void setY(double y1);
	
	double getY();
	
private:
	double x;
	double y;
};

头文件circle.h:

//circle.h
#pragma once
#include <iostream>
using namespace std;
#include "point.h"
class Circle
{
    
    
public:
	void setR(double r1);
	double getR();
	//定义圆心,既然是点,就用Point类,然后返回类型也用Point类
	void setCenter(Point center1);
	Point getCenter();

private:
	double r;
	//要先定义了一个Point类,才能使用Point center
	Point center;
};


point.cpp:

//point.cpp
#include "point.h"
	void Point::setX(double x1)
	{
    
    /*如果不加Point::,这就是一个全局函数,但事实上,它是一个成员函数。
	所以要加上一个作用域Point::,来表示它是Point作用域下面的成员函数。*/
		x=x1;
	}
	double Point::getX()
	{
    
    
		return x;
	}
	void Point::setY(double y1)
	{
    
    
		y=y1;
	}
	double Point::getY()
	{
    
    
		return y;
	}

circle.cpp:

//circle.cpp
#pragma once
#include <iostream>
using namespace std;
#include "point.h"
#include "circle.h"
	void Circle::setR(double r1)
	{
    
    
		r=r1;
	}
	double Circle::getR()
	{
    
    
		return r;
	}
	//定义圆心,既然是点,就用Point类,然后返回类型也用Point类
	void Circle::setCenter(Point center1)
	{
    
    
		center=center1;
	}
	Point Circle::getCenter()
	{
    
    
		return center;
	}


main.cpp:

//main.cpp
#include<iostream>
using namespace std;
#include "circle.h"
#include "point.h"
//判断圆心与点的距离:
void isCircle(Circle &c,Point &p)//最好用引用传递,虽然我也不清楚为什么
{
    
    
	double R=c.getR()*c.getR();//这里忘了加括号,找了很久的错误。
	double distance=(c.getCenter().getX()-p.getX())*(c.getCenter().getX()-p.getX())
		+(c.getCenter().getY()-p.getY())*(c.getCenter().getY()-p.getY());
	/*
	wdnmd,这个程序搞了一下午,一直不知道哪里有错误,因为VS不会自动填充括号,
	我一直找了很久的错误,醉了,以后一定要记得加括号
	*/
	if(distance==R)
	{
    
    
		cout<<"点在圆上"<<endl;
	}
	else if(distance>R)
	{
    
    
		cout<<"点在圆外"<<endl;
	}
	else
	{
    
    
		cout<<"点在圆内"<<endl;
	}


}
int main()
{
    
    
	//创建圆:
	Circle c;
	c.setR(5.0);
	Point center;
	center.setX(0.0);
	center.setY(0.0);
	//我们要先设置一个点,然后再创建圆心。给圆心赋值
	c.setCenter(center);

	//创建点:
	Point p;
	p.setX(3.0);
	p.setY(4.0);
	isCircle(c,p);
	system("pause");
}

我们再来详细说一下格式:
我一开始是用到devc++,我发现这个弄头文件很麻烦,于是从今天开始用VS了。
在这里插入图片描述
1、在头文件中创建point.h和circle.h。然后首要输入的是“ #pragma once ”,防止头文件重复包含。
2、头文件主要是函数的声明以及变量的声明。
3、在cpp文件中,必须要包含它本身的头文件,如果cpp中还用到了其他函数,则还要再加上引用函数的头文件。cpp文件主要是函数的实现操作,不用加int main()。
例:在circle.cpp中,就用到了#include “point.h”、#include "circle.h"两个头文件。

2、对象的初始化和清理

2.1 构造函数和析构函数

构造函数语法: 类名( ){ }
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名( ){ }
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前加上符号~
3.析构函数不可以有参数,因此不可以发生重载
4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

#include <iostream>
#include <string> 
using namespace std; 
class Person
{
    
    
public: 
   /*1.构造函数没有返回值也不写void
     2.函数名称与类名相同
     3.构造函数可以有参数,可以发生重载
     4.创建对象的时候,构造函数会自动调用,而且只会调用一次
   */	
   Person()//构造函数 
   {
    
    
     cout<<"Person 构造函数的调用"<<endl;	
   } 
   
   /*1.析构函数没有返回值也不写void
     2.函数名称与类名相同,在名称前加上符号~
     3.析构函数不可以有参数,不可以发生重载
     4.对象销毁前会自动调用析构函数,而且只会调用一次
   */
    ~Person()//析构函数 
   {
    
    
     cout<<"~Person 析构函数的调用"<<endl;	
   } 
};
/*构造和析构都是必须有的实现,如果我们自己不提供,
编译器会提供一个 */
void test01()
{
    
    
	Person p;//在栈上的数据,test01执行完毕后,释放这个对象。 
}
int main()
{
    
    
    test01(); 
}


在这里插入图片描述

2.2 构造函数分类及调用

1、构造函数的分类
按照参数分类: 无参构造(默认构造)和有参构造
按照类型分类: 普通构造函数和拷贝构造函数
2、调用方法
(1)括号法(一般用这个)
(2)显示法
(3)隐式转换法
3、注意事项:
(1)调用默认构造函数的时候,不要加(), 因为 Person p1(),编译器会认为是一个函数的声明,
不会认为在创建对象。
(2)不要利用拷贝构造函数初始化匿名对象 Person(p3),不然编译器会认为Person(p3)===Person p3

#include <iostream>
#include <string> 
using namespace std; 
class Person
{
    
    
	//对象销毁前会自动调用析构函数,而且只会调用一次
public: 
   //构造函数的分类及调用	
   //按照参数分类:   无参构造(默认构造)和有参构造 
   //按照类型分类:   普通构造函数和拷贝构造函数 
   //构造函数
   int age;
   Person() 
   {
    
    
    cout<<"Person 无参构造函数的调用"<<endl;	
   }
   
   Person(int a) 
   {
    
    
   	age=a;
    cout<<"Person 有参构造函数的调用"<<endl;	
   } 
  
    ~Person()//析构函数 
   {
    
    
     cout<<"~Person 析构函数的调用"<<endl;	
   } 
   //拷贝构造函数
   Person(const Person &p)
   {
    
    
    	//将传入的人身上的所有属性,拷贝到我身上
		age=p.age;
		cout<<"Person 拷贝构造函数的调用"<<endl;
   } 
   
};

void test01()
{
    
    
    //1.括号法
    cout<<"1.括号法:"<<endl; 
	Person p1;//默认构造函数调用
	Person p2(10); //有参构造函数 
	Person p3(p2); //拷贝构造函数
	
	//注意事项:
	/*调用默认构造函数的时候,不要加()
	因为 Person p1(),编译器会认为是一个函数的声明,
	不会认为在创建对象*/ 
	cout<<"p2的年龄为:"<<p2.age<<endl;
	cout<<"p3的年龄为:"<<p3.age<<endl;
	
	cout<<endl;
    //2.显示法 
    cout<<"2.显示法:"<<endl;
	Person p4;//默认构造函数调用
	Person p5=Person(10); //有参构造函数
	Person p6=Person(p5); //拷贝构造函数
	
	Person(10);/*匿名对象
	特点:当前行执行结束后,系统会立即回收掉匿名对象*/ 
	cout<<"aaaa"<<endl;
	
	//注意事项2
	/*不要利用拷贝构造函数 初始化匿名对象
	Person(p3),编译器会认为Person(p3)===Person p3;*/ 
	
	cout<<endl;
	//3.隐式转换法 
	cout<<"3.隐式转换法:"<<endl;
	Person p7=10;//相当于Person p4=Person(10)
	Person p8=p7;//拷贝构造 
}
int main()
{
    
    
    test01(); 
}


在这里插入图片描述

2.3 拷贝构造函数的调用时机

1、通常有三种情况:
(1)使用一个已经创建完毕的对象来初始化一个新对象。
(2)值传递的方式给函数参数传值。
(3)以值方式返回局部对象。

#include <iostream>
using namespace std;
class Person
{
    
    
public:
	Person()
	{
    
    
		cout<<"Person默认构造函数的调用"<<endl;
	}
	Person(int age1)
	{
    
    
		age=age1;
	}
	Person(const Person &p)
	{
    
    
		age=p.age;
		cout<<"Person拷贝构造函数的调用"<<endl;
	}
	~Person()
	{
    
    
		cout<<"Person析构函数的调用"<<endl;
	}
	int age;
};
//(1)使用一个已经创建完毕的对象来初始化一个新对象。
void test01()
{
    
    
	Person p1(20);
	Person p2(p1);
	cout<<"P2的年龄为:"<<p2.age<<endl;
}

//(2)值传递的方式给函数参数传值。
void work(Person p)
{
    
    

}
void test02()
{
    
    
	Person p;
	work(p);
}

//(3)以值方式返回局部对象。
Person work2()
{
    
    
	Person p1;
	cout<<(int*)&p1<<endl;
	return p1;
}
void test03()
{
    
    
	Person p=work2();
    cout<<(int*)&p<<endl;
}
int main()
{
    
    
	test03();
	system("pause");
	return 0;
}

2.4 构造函数的调用规则

构造函数的调用规则:
(1)创建一个类,c++编译器会给每个类都添加至少三个函数
默认构造(空实现)
析构构造(空实现)
拷贝构造(值拷贝)
(2)如果我们写了有参构造函数,编译器就不再提高默认构造,依然提供拷贝构造。
(3)如果我们写了拷贝构造函数,编译器就不再提供其他普通构造函数了。
简而言之:1.有参则不提供默认构造,但提供拷贝构造。2.有拷贝构造则不提供默认构造和有参构造。
1、创建一个类,c++编译器会给每个类都添加至少三个函数
默认构造(空实现)
析构构造(空实现)
拷贝构造(值拷贝)

#include <iostream>
using namespace std;

//构造函数的调用规则
//1、创建一个类,c++编译器会给每个类都添加至少三个函数
//默认构造(空实现)
//析构构造(空实现)
//拷贝构造(值拷贝)

class Person
{
    
    
public: 
	Person()
	{
    
    
		cout<<"Person的默认构造函数调用"<<endl;
	}
	Person(int age1)
	{
    
    
		age=age1;
		cout<<"Person的有参构造函数调用"<<endl;
	}
	Person(const Person &p)
	{
    
    
		cout<<"Person的拷贝构造函数调用"<<endl;
		age=p.age;
	}
	~Person()
	{
    
    
		cout<<"Person的析构函数调用"<<endl;
	}
	int age;
};
void test01()
{
    
    
	Person p;//Person的默认构造函数调用
	p.age=18;//Person的拷贝构造函数调用
	Person p2(p);
	cout<<"p2的年龄为:"<<p2.age<<endl;
}

int main()
{
    
    
	test01();

	system("pause");
	return 0;
}

在这里插入图片描述

2、如果我们写了有参构造函数,编译器就不再提高默认构造,依然提供拷贝构造。

#include <iostream>
using namespace std;

//构造函数的调用规则

//2、如果我们写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造。
class Person
{
    
    
public: 
	/*Person()
	{
		cout<<"Person的默认构造函数调用"<<endl;
	}*/
	Person(int age1)
	{
    
    
		age=age1;
		cout<<"Person的有参构造函数调用"<<endl;
	}
	/*Person(const Person &p)
	{
		cout<<"Person的拷贝构造函数调用"<<endl;
		age=p.age;
	}*/
    //相当于系统自己调用了age=p.age
	~Person()
	{
    
    
		cout<<"Person的析构函数调用"<<endl;
	}
	int age;
};
void test02()
{
    
    
	Person p(28);
	Person p2(p);
	cout<<"p2的年龄为:"<<p2.age<<endl;
}

int main()
{
    
    
	test02();

	system("pause");
	return 0;
}

在这里插入图片描述

2.5 深拷贝与浅拷贝

1、浅拷贝:简单的赋值拷贝操作。
2、深拷贝:在堆区重新申请空间,进行拷贝操作。
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

#include <iostream>
using namespace std;
//浅拷贝:编译器提供的等号赋值操作:height=p.height
//浅拷贝带来的问题就是堆区的内存重复释放,用深拷贝解决。
//深拷贝:我们要重新在堆区创建一块内存
class Person
{
    
    
public:
	Person()
	{
    
    
		cout<<"Person的默认构造函数的调用"<<endl;
	}
	Person(int age1,int height1)
	{
    
    
		age=age1;
		height=new int(height1);//用new int是将身高是数据创建在堆区。
		cout<<"Person的有参构造函数的调用"<<endl;
	}

	//自己实现拷贝构造函数,解决浅拷贝带来的问题
	Person(const Person &p)
	{
    
    
		cout<<"Person拷贝构造函数的调用"<<endl;
		age=p.age;
		//height=p.height;编译器默认实现就是这一行代码
		//深拷贝操作:
		height=new int(*p.height);
	}

	~Person()
	{
    
    
		//析构代码,将堆区开辟的数据做释放操作。
		if(height!=NULL)
		{
    
    
			delete height;
			height=NULL;
		}
		cout<<"Person的析构函数的调用"<<endl;
	}
	int age;
	int *height;//用指针的目的是将身高的数据开辟到堆区。

};
void test01()
{
    
    
	Person p1(18,160);
	cout<<"p1的年龄有多大:"<<p1.age<<",身高为:"<<*p1.height<<endl;
	//用指针去接收堆区的数据。
	Person p2(p1);/*先用拷贝构造函数释放指针height,再height=new int(*p.height);
	这样析构函数释放的指针height就是不同的位置*/
	cout<<"p2的年龄有多大:"<<p2.age<<",身高为:"<<*p2.height<<endl;
}
int main()
{
    
    
	test01();

	system("pause");
	return 0;
}

在这里插入图片描述

2.6 初始化列表

1、作用:c++提供了初始化列表语法,用来初始化属性。
2、语法:构造函数():属性(值1),属性(值2)…{ }
主要是记住这个格式。

#include <iostream>
using namespace std;
class Person
{
    
    
public:
	//传统初始化操作
	Person(int a1,int b1,int c1)
	{
    
    
		a=a1;
		b=b1;
		c=c1;
	}

	int a;
	int b;
	int c;
};
class Person1
{
    
    
public:
	
	//初始化列表初始化属性
	//语法:构造函数():属性(值1),属性(值2)...{ }
	Person1(int a2,int b2,int c2):a(a2),b(b2),c(c2){
    
     }

	int a;
	int b;
	int c;
};
void test01()
{
    
    
	cout<<"传统初始化操作:"<<endl;
	Person p1(10,20,30);
	cout<<"a="<<p1.a<<endl;
	cout<<"b="<<p1.b<<endl;
	cout<<"c="<<p1.c<<endl;
	cout<<"初始化列表初始化属性:"<<endl;
	Person1 p(4,7,9);
	cout<<"a="<<p.a<<endl;
	cout<<"b="<<p.b<<endl;
	cout<<"c="<<p.c<<endl;
}
int main()
{
    
    
	//初始化列表
	test01();
	system("pause");
	return 0;
}

在这里插入图片描述

2.7 类对象作为成员

1、c++类中的成员可以是另一个类的对象,我们称该成员为对象成员。
2、当其他类对象作为本类成员,构造的时候先构造对象,再构造自身。而析构的顺序与构造相反。

#include <iostream>
#include <string>
using namespace std;
//类对象作为成员
class Phone
{
    
    
public:
	Phone(string Pname1):Pname(Pname1)
	{
    
    
		cout<<"Phone的构造函数的调用"<<endl;
	}
	~Phone()
	{
    
    
		cout<<"Phone的析构函数的调用"<<endl;
	}
	//手机名:
	string Pname;
	
};
class Person
{
    
    
public:
	Person(string name1,string pName2 ):name(name1),phone(pName2)
	{
    
    
		cout<<"Person的构造函数的调用"<<endl;
	}
	~Person()
	{
    
    
		cout<<"Person的析构函数的调用"<<endl;
	}
	//姓名:
	string name;
	//手机:
	Phone phone;//当其他类对象作为本类成员,构造的时候先构造对象,再构造自身。而析构的顺序与构造相反。
};
void test01()
{
    
    
	Person p("张三","华为");
	cout<<p.name<<"拿着"<<p.phone.Pname<<"手机"<<endl;
	/*这里要注意,因为Phone是我们构造的类,所以我们要输出手机名,还要
	将Phone类中的Pname输出,就要调用两次,p.phone.Pname*/
}
int main()
{
    
    
	test01();//主函数中要调用。
	system("pause");
	return 0;
}

在这里插入图片描述

2.8 静态成员函数

1、静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
2、静态成员分为:静态成员变量和静态成员函数。
3、静态成员变量:
(1)所有对象共享同一份数据
(2)在编译阶段分配内存
(3)类内声明,类外初始化
4、静态成员函数:
(1)所有对象共享同一个函数
(2)静态成员函数只能访问静态成员变量
5、静态成员函数也有访问权限。

#include <iostream>
using namespace std;
//静态成员函数
class Person
{
    
    
public:
	//静态成员函数
	static void func()
	{
    
    
		a=100;//可以在函数体内修改静态成员变量。
		cout<<"static void func()的调用"<<endl;
		cout<<"a="<<a<<endl;
	}
	static int a;//静态成员变量
};
 int Person::a=10;//类外初始化要写它的作用域
void test01()
{
    
    
	//1、通过对象进行访问
	cout<<"1、通过对象进行访问"<<endl;
	Person p;
	p.func();
	//2、通过类名进行访问
	cout<<"2、通过类名进行访问"<<endl;
	Person::func();
}
int main()
{
    
    
	
	test01();
	system("pause");
	return 0;
}

在这里插入图片描述

3、C++对象模型和this指针

3.1 成员变量和成员函数分开存储

1、只有非静态成员函数才属于类的对象
2、空对象占用内存空间为: 1。
3、C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置,每个空对象也应该有一个独一无二的内存地址。

#include <iostream>
using namespace std;
class Person
{
    
    
//空对象占用内存空间为: 1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址,

};
class Person2
{
    
    
	int a;//非静态成员变量 属于类的对象上
	static int b;//静态成员变量 不属于类对象上
	void func() {
    
    };//非静态成员函数 不属于类对象上
	static void func2() {
    
    };//静态成员函数  不属于类对象上

};
int Person2::b=0;
void test01()
{
    
    
	Person p;
	cout<<"size of (p)="<<sizeof(p)<<endl;
}
void test02()
{
    
    
	Person2 p2;
	cout<<"size of (p2)="<<sizeof(p2)<<endl;
}

int main()
{
    
    
	cout<<"test01():"<<endl;
	test01();//空对象占用内存空间为: 1
	cout<<"test02():"<<endl;
	test02();
	system("Pause");
	return 0;
}

在这里插入图片描述

3.2 this指针的使用

1、this指针指向被调用的成员函数所属的对象。
2、this指针是隐含每一个非静态成员函数内的一 种指针。
3、this指针不需要定义,直接使用即可
4、this指针的用途::
(1)当形参和成员变量同名时,可用this指针来区分。
(2)在类的非静态成员函数中返回对象本身,可使用return *this。

#include <iostream>
using namespace std;
//1、解决名称冲突
class Person
{
    
    
public:
	Person(int age)
	{
    
    
		//this指针指向 被调用的成员函数 所属的对象
		this->age=age;//当形参与成员变量同名时,可用this指针来区分
	}

	//如果用*this返回它的本体,就要用person的引用方式作为返回。
	Person& PersonaddAge(Person &p)//只能用引用返回,不能用值返回。
	{
    
    
		this->age+=p.age;
		//this指向p2的指针,而*this指向的就是p2这个对象本体
		return *this;
	}
	int age;
};
void test01()
{
    
    
	Person p1(18);
	cout<<"p1的年龄为:"<<p1.age<<endl;
}
void test02()
{
    
    
	Person p2(10);
	Person p3(10);
	//链式编程思想:
	p3.PersonaddAge(p2).PersonaddAge(p2).PersonaddAge(p2);
	cout<<"p3的年龄是:"<<p3.age<<endl;

}
//2、返回对象本身用*this
int main()
{
    
    
	test01();
	test02();
	system("Pause");
	return 0;
}

在这里插入图片描述

3.3 空指针访问成员函数

1、c++中空指针也是可以调用成员函数的,但是要注意有没有用到this指针,如果用到this指针,需要加以判断来保证代码的健壮性。

#include <iostream>
using namespace std;
//空指针调用成员函数
class Person
{
    
    
public:
	void showClassName()
	{
    
    
		cout<<"this is Person class"<<endl;
	}
	void showPersonAge()
	{
    
    
		if(this==NULL)
		{
    
    
			return;
		}
		cout<<"age=="<<age<<endl;
	}
	int age;
};
void test01()
{
    
    
	Person *p=NULL;
	p->showClassName();
	p->showPersonAge();
}
int main()
{
    
    
	test01();
	system("pause");
	return 0;
}

3.4 const修饰成员函数

1、常函数:
(1)成员函数后加const后我们称为这个函数为常函数
(2)常函数内不可以修改成员属性
(3)成员属性声明时加关键字mutable后, 在常函数中依然可以修改
2、常对象:
(1)声明对象前加const称该对象为常对象
(2)常对象只能调用常函数

#include <iostream>
using namespace std;
//常函数
class Person
{
    
    
public:
	//this指针的本质 是指针常量 指针的指向是不可以修改的
	//在成员函数后面加const,修饰的是this指向。让指针指向的值也不可以修改。
	//成员函数后加const后我们称为这个函数为常函数
	//常函数:
	void showPerson() const
	{
    
    
		this->b=100;
		//this->a=100;
		//this=NULL;   this指针不可以修改指针的的指向
	}
	void func()
	{
    
    
	
	};
	int a;
	//加关键字mutale
	mutable int b;//特殊变量,即使在常函数中,也可以修改这个值。
};
void test01()
{
    
    
	Person p;
	p.showPerson();
}
//常对象
void test02()
{
    
    
	const Person p;//在对象前加一个const,变成常对象。
	//p.a 常对象不允许修改
	p.b=50;//是特殊值,可以修改

	//常对象只能调用常函数。
	p.showPerson();
	//p.func();  常对象不可以调用普通的成员函数,因为普通成员函数可以修改属性。

}
int main()
{
    
    

	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45960449/article/details/112061275
今日推荐