C++核心:面向对象
一维数组定义:
int arr[3] 初始化必须有长度
const 修饰指针
int a = 10;
int b = 20;
const int *p = &a;
p = &b;
常量指针特点:指针的指向可以修改,但是指针指向的值不可以改
、
const修饰常量
int a = 10;
int * const p = &a;
指针常量特点:指针的指向不可以改,指针指向的值可以改
*p = 20;
const int* const p = &a;
特点:指针的指向和指针指向的值都不可以改
c++程序在执行时,将内存大方向划分为4个区域
代码区:存放函数体的二进制代码,由操作系统进行管理
全局区:存放全局变量和静态变量以及常量(main函数外面的:int a; static int a; const int a)
栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存4区的意义:
不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。
例:
int* func()
{
int a = 10; //局部变量,存放在栈区,栈区的数据在函数执行完后自动释放
return &a;
}
int main()
{
int* p = func();
//cout<<*p<
int* func()
{
//利用new关键字 可以将数据开辟到堆区
//指针 本质也是局部变量,放在栈上,但是指针保存的数据是放在堆区
int *p = new int(10);
return p;
}
int main()
{
//在堆区开辟数据
int* p = func();
cout<<*p<
//1. new的基本语法
int* func()
{
//在堆区创建整型数据
//new返回的是 该数据类型的指针
int *p = new int(10);
return p;
}
void test01()
{
//在堆区开辟数据
int* p = func();
cout<<*p<
int a = 10;
int &b = a;
// b是a的别名
注意:引用必须初始化,且一旦初始化后,不可以更改
void func(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
void func2(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a = 10;
int b = 20;
//引用传递
func(a, b);
//地址传递
func2(&a, &b);
}
此方法不需要指针传递,该方法在C语言中不可用
引用的本质在C++内部的实现是一个指针常量。
int& ref = a;
编译器会转换为 int* const ref = &a;
//发现是引用,转换为 int* const ref = &a;
void func(int& ref)
{
ref = 100;
}
int main()
{
//自动转换为 int* const ref = &a; 指针常量是指针指向不可改,故引用是不可更改的
int& ref = a;
ref = 20; //内部发现ref是引用,自动转换为 *ref = 20;
cout<<"a:"<
作用:常量引用主要用来修饰形参,防止误操作
const int* ref = 10;
加上const之后,编译器将代码修改 int temp = 10; const int& ref=temp;
修改为常量指针
void show(const int& v)
{
//v += 10;
cout<
重载满足条件:
注意事项:函数的返回值不可以作为函数重载的条件。。。
void func(int &a) //int &a = 10不合法,赋值必须是一个合法地址
{
cout<<"func(int &a)调用"<
C++面向对象的三大特性:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为
在设计类的时候,属性与行为写在一起,表现事物。
语法
class 类名{访问权限:属性/行为};
例:设计圆类
const PI = 3.14;
class Circle
{
//访问权限
//公共权限
public:
//属性--半径
int m_r;
//行为--获取圆的周长
double calculateZC()
{
return 2* m_r * PI;
}
};
int main()
{
//通过圆类,创建具体的圆(对象)
Circle c1;
//给圆对象的属性进行赋值
c1.m_r = 10;
cout<<"圆的周长为:"<
方法2:通过函数给属性赋值
类中的属性和行为 统一称为 成员
属性:也叫成员属性和成员变量
行为:也叫成员函数和成员方法
Class Student
{
//访问权限 -- 公开权限
public:
//属性
string m_Name;
string m_Id;
//行为
//给姓名赋值,用行为给属性赋值
void setName(string name)
{
m_Name = name;
}
void setId(string id)
{
m_Id = id;
}
void showStudent()
{
cout<<"学生姓名为:"<
访问权限有三种:
公共权限 public 此权限类内和类外都可以访问成员
保护权限 protected 此权限类内访问成员,类外不可以,但儿子可以访问父亲中的保护内容
私有权限 private 此权限类内访问成员,类外不可以,儿子也不可以
class Preson
{
public:
string m_Name; //姓名
protected:
string m_Car; //汽车
private:
int m_Password; //银行卡密码
public:
//类内可以访问
void func()
{
m_Name = "张三";
m_Car = "拖拉机";
m_Password = 213;
}
}
struct和class区别
默认的访问权限不同
成员属性设为私有
成员属性一般会设为私有,优点有二。
优点1: 将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
class Person
{
public:
//设置姓名
void setName(string name)
{
m_Name = name;
}
void getName()
{
return m_Name;
}
// 获取年龄 只读
int getAge()
{
m_Name = 0;
return m_Name;
}
void m_Lover(string lover)
{
lover = m_lover;
}
private:
// 姓名 可读可写
string m_Name;
// 年龄 只读
string m_Age;
//情人 只写
string m_Lover;
}
int main()
{
Person p;
p.setName("张三");
cout<<"姓名为:"<
修改---- 年龄可读可写 且如果修改年龄范围必须在 0~100
int getAge()
{
return m_Age;
}
//设置年龄以及范围
void setAge(int age)
{
if(age<0 || age>100)
{
cout<<"范围不合适!"<
创建立方体类,并分别用全局函数和成员函数判断两个立方体是否相等
class Cube
{
public:
void setL(int l)
{
m_l = l;
}
void setW(int w)
{
m_w = w;
}
void setH(int h)
{
m_h = h;
}
void getL()
{
return m_l;
}
void getW()
{
return m_w;
}
void getH()
{
return m_h;
}
bool isSameByClass(Cube& c)
{
if(m_l==c.getL() && m_w==c.getW() && m_h==c.getH())
{
return true;
}
return false;
}
private:
int m_l;
int m_w;
int m_h;
}
bool isSame(Cube& c1, Cube& c2)
{
if(c1.getL(==c2.getL() && c1.getW()==c2.getW() && c1.getH()==c2.getH())
{
return true;
}
return false;
}
案例:查看点和圆的关系
分别设置两个类:一个是圆类,一个是点类,其中点类中存放点的x和y
Point.h
#pragma once
using namespace std;
#include
class Point
{
public:
void setX(int x);
void setY(int y);
int getX();
int getY();
private:
int m_X;
int m_Y;
};
Point.cpp
#include "Point.h"
void Point:: setX(int x)
{
m_X = x;
}
void Point::setY(int y)
{
m_Y = y;
}
int Point::getX()
{
return m_X;
}
int Point::getY()
{
return m_Y;
}
Circle.h
#pragma once
#include "Point.h"
class Circle
{
public:
void setR(int r);
void setCenter(Point center);
int getR();
Point getCenter();
void isInCircle(Circle& c, Point p);
private:
int m_R;
Point m_Center;
};
Circle.cpp
#include "Circle.h"
void Circle::setR(int r)
{
m_R = r;
}
void Circle::setCenter(Point center)
{
m_Center = center;
}
int Circle::getR()
{
return m_R;
}
Point Circle::getCenter()
{
return m_Center;
}
void Circle::isInCircle(Circle& c, Point p)
{
// 计算两点之间的距离的平方
int distance = (c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
//计算半径 的平方
int rDistance = c.getR() * c.getR();
if (distance == rDistance)
{
cout << "点在圆上" << endl;
}
else if (distance > rDistance)
{
cout << "点在圆外" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}
main.cpp
#include
#include "Circle.h"
#include
using namespace std;
int main()
{
Circle c;
Point center;
center.setX(10);
center.setY(10);
Point p;
p.setX(0);
p.setY(11);
c.setCenter(center);
c.setR(10);
c.isInCircle(c, p);
}
每个对象都会有初始设置及对象销毁前的清理数据设置。
如果不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){} 构造函数没有返回值,不写void,但可以有参数,可发生重载,自动调用
析构函数语法:~类名(){} 析构函数没有返回值,不写void,不可以i有参数,不可重载。 自动调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐式转换法
分类方式:
Class Person
{
public:
Person()
{
cout<<"无参构造函数调用"<
调用方式
//无参构造函数调用
void test01()
{
Person p1;
}
//有参构造函数调用
void test02()
{
//方法1. 括号法
Person p1(10); //有参构造
Person p2(p1); //拷贝构造
// 注意事项1
//Person p(); //不可以这样调用,它会认为是对函数的声明,不会认为是在创建对象
//方法2. 显示法
Person p3 = Person(10);
Person p4 = Person(p3);
//方法3. 隐式转换法
Person p5 = 10;// 相当于写了 Person p5 = Person(10);
Person p6 = p5;// 相当于写了 Person p6 = Person(p5);
//匿名对象
Person(10);
//注意事项2 不要利用拷贝构造函数初始化匿名对象
//Person(p1);//编译器会认为 Person(p1)==Person p1;然而上面已经定义了p1,参数被重新定义会报错
}
//2. 值传递方式给函数参数传值
void doWork(Person p)
{
}
//3. 值方式返回局部对象
Person reWork()
{
Person p;
return p;
};
void test02()
{
/*Person p1;
doWork(p1);*/
Person p = reWork();
//1. 使用一个已经创建完毕的对象来初始化一个新对象
Person p1;
Person p2(p1);
}
默认情况下,C++编译器至少给一个类添加3个函数
调用规则:
如果用户定义有参构造函数,C++不会再提供默认构造函数,但会提供默认拷贝构造函数。
如果拥堵定义拷贝构造函数,C++不会提供其他构造函数。
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
将对象的属性内容创建在堆区
Class Person
{
public:
Person(int age, int height)
{
age = m_Age;
m_Height = new int(height);
cout<<"有参构造函数调用"<
出错原因:浅拷贝只拷贝地址,而当p2调用析构函数销毁指向的堆区0x0011后,p1也会调用析构函数继续销毁堆区0x0011,因为m_Height存放的地址相同。然而现在0x0011堆区已经被销毁,故会出错。
深拷贝:
Person(const Person& p)
{
//将传入的人身上的所有属性,拷贝到我身上
m_Age = p.m_Age;
//m_Height = p.m_Height; //编译器默认实现的拷贝构造函数
//深拷贝
m_Height = new int(*p.m_Height);
cout << "Person的拷贝构造调用" << endl;
};
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
作用:C++提供了初始化列表语法,用来初始化属性
Class Person
{
public:
//传统方式初始化
Person(int a, int b, int c)
{
m_A = a;
m_B = b;
m_C = c;
}
//初始化列表
// Person(): m_A(10), m_B(20), m_C(30)
// {
// }
//或
Person(int a, int b, int c): m_A(a), m_B(b), m_C(c)
{
}
int m_A;
int m_B;
int m_C;
};
类对象作为类成员
class Phone
{
public:
Phone(string name)
{
name = m_PhoneName;
}
string m_PhoneName;
};
class Person2
{
public:
//相当于 Phone m_Phone = phone_Name; 隐式转换法
Person2(string name, string phone_Name) : m_Name(name), m_Phone(phone_Name)
{
}
string m_Name;
Phone m_Phone;
};
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身;
析构的顺序与之相反。
静态成员分为静态成员变量和静态成员函数
class Person
{
static int m_A;
};
int Person::m_A = 100;
void test01()
{
Person p;
//100
cout<
静态成员变量有两种访问方式
//1. 通过对象进行访问
Person p;
cout<
静态成员函数也有两种访问方式
void test()
{
Person p;
p.func();
Person::func();
}
只能访问静态成员变量
class Person
{
public:
static void func()
{
m_A = 100; 静态成员函数 可以访问 静态成员变量
//m_B = 200; //静态成员函数 不可以访问 非静态成员变量,因为无法区分到底是哪个对象的变量
cout<<"stataic void func调用"<
class Person
{
};
void test
{
Person p;
//空对象占用内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
cout<<"size of p = "<
class Person
{
int m_A; //非静态成员变量 属于类的对象上
static int m_B; //静态成员变量 不属于类的对象上
void func(){} //非静态成员函数 不属于类对象上
static void func2(){} //静态成员函数,属于类的对象上
};
void test
{
Person p;
//对象占用内存空间为:4
cout<<"size of p = "<
只有非静态成员变量属于类的对象上
this指针指向 被调用的成员函数 所属的对象
用途1. 解决名称冲突
class Person
{
Person(int age)
{
this->age = age;
}
int age;
};
void test01()
{
Person p1(18);
cout<<"p1年龄:"<
用途2:返回对象本身用*this
class Person
{
Person(int age)
{
this->age = age;
}
Person& PersonAddAge(Person& p)
{
this->age += p.age;
//this指向p2的指针,而*this指向的就是p2这个对象本体
return *this;
}
int age;
};
void test01()
{
Person p1(10);
Person p2(10);
//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout<<"p2年龄:"<
class Person
{
public:
void showClassName()
{
cout<<"this is Person class"<showClassName();
p->showPersonAge();// 该行报错
}
常函数:
常对象:
//常函数
class Person3 {
public:
//this 指针的本质是指针常量,指针的指向是不可以修改的,但指向的值可以修改
//Person* const this;
//如果想让指向的值也不可修改,则需要改为 const Person* const this;
//在成员函数后面加const,修饰的是this指向,如果想让指向的值也不可修改
void showPerson() const
{
this->m_B = 100;
//this->m_A = 100;
//this = NULL; //this指针不可以修改指针的指向
}
void func()
{
m_A = 100;
}
int m_A;
mutable int m_B; //特殊变量,即使在常函数中,也可以修改这个值,加上关键字mutable
};
void test05()
{
Person3 p;
p.showPerson();
}
//常对象
void test06()
{
const Person3 p;//在对象前加const,变为常对象
//p.m_A = 100;
p.m_B = 100; //m_B是特殊值,在常对象下也可以修改
//常对象只能调用常函数
//p.func(); //常对象 不可以调用普通成员函数,因为普通成员函数可以修改属性
}