记录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;
}