C++基础

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<<endl; //不可以这样写
}

int* func()
{
    //利用new关键字 可以将数据开辟到堆区
    //指针 本质也是局部变量,放在栈上,但是指针保存的数据是放在堆区
    int *p = new int(10);
    return p;
}
int main()
{
    //在堆区开辟数据
    int* p = func();
    cout<<*p<<endl; //可以这样写
}

new 操作符

//1. new的基本语法
int* func()
{
    //在堆区创建整型数据
    //new返回的是 该数据类型的指针
    int *p = new int(10);
    return p;
}
void test01()
{
    //在堆区开辟数据
    int* p = func();
    cout<<*p<<endl;
    //释放堆区的数据利用关键字delete
    delete p;
}
//在堆区利用new开辟数组
void test02()
{
    //在堆区创建10个整型数据的数组
    int* arr = new int[10];
    for(int i=0; i<10; ++i)
    {
        arr[i] = i+100;
    }
    for(int i=0; i<10; ++i)
    {
        cout<<arr[i]<<endl;
    } 
    //释放堆区数组 要加[]
    delete[] arr;
}

int main()
{
    test01();
    test02();
}

引用

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:"<<a<<endl;
    cout<<"ref:"<<ref<<endl;
    func(a);
    cout<<"ref:"<<ref<<endl;
    return 0;
}

常量引用

作用:常量引用主要用来修饰形参,防止误操作

const int* ref = 10;

加上const之后,编译器将代码修改 int temp = 10; const int& ref=temp;

修改为常量指针

void show(const int& v)
{
    //v += 10;
    cout<<v<<endl;
}
int main()
{
    // 加上const之后,编译器将代码修改 int temp = 10; const int& ref=temp;
    const int& ref = 10;
    int a = 10;
    show(a);
}

函数重载

重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同,或者个数或顺序不同

注意事项:函数的返回值不可以作为函数重载的条件。。。

void func(int &a) //int &a = 10不合法,赋值必须是一个合法地址
{
    cout<<"func(int &a)调用"<<endl;
}

void func(const int &a) // const int &a = 10 合法 因为编译器会修改为 int temp = 10; const int& a = temp;
{
    cout<<"func(const int &a)调用"<<endl;
}
int main
{
    int a = 10;
    func(a); //调用第一个func
    func(10); // 调用第二个func,因为如果10调用第一个的话,int &a = 10不合法
}

类和对象

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<<"圆的周长为:"<<c1.calculateZC()<<endl;
    system("pause");
    return 0;
}

方法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<<"学生姓名为:"<<m_Name<<",ID为:"<<m_Id<<endl;
    }
    
    
};

int main()
{
    //实例化对象
    Student s1;
    s1.setName("张三");
    //s1.m_Name = "张三";
    s1.showStudent();
    
}
封装意义二

访问权限有三种:

公共权限 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区别

默认的访问权限不同

  • 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<<"姓名为:"<<p.getName<<endl;
    cout<<p.getName()<<endl;;
    p.setLover("nn");
}

修改---- 年龄可读可写 且如果修改年龄范围必须在 0~100

int getAge()
{
    return m_Age;
}
//设置年龄以及范围
void setAge(int age)
{
    if(age<0 || age>100)
    {
        cout<<"范围不合适!"<<endl;
        return;
    }
    m_Age = age;
}

创建立方体类,并分别用全局函数和成员函数判断两个立方体是否相等

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<iostream>
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<stdio.h>
#include "Circle.h"
#include <iostream>
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<<"无参构造函数调用"<<endl;
    }
    Person(int a)
    {
        cout<<"有参构造函数调用"<<endl;
        age = a;
    }
    Person(const Person& p)
    {
        age = p.age;
        cout<<"拷贝构造函数调用"<<endl;
    }
    ~Person()
    {
        cout<<"析构函数调用"<<endl;
    }
    int age;
};

调用方式

//无参构造函数调用
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,参数被重新定义会报错
}
拷贝构造函数调用时机
  1. 使用一个已经创建完毕的对象来初始化一个新对象
  2. 值传递方式给函数参数传值
  3. 值方式返回局部对象
//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个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝

调用规则:

如果用户定义有参构造函数,C++不会再提供默认构造函数,但会提供默认拷贝构造函数。

如果拥堵定义拷贝构造函数,C++不会提供其他构造函数。

深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请空间,进行拷贝操作

将对象的属性内容创建在堆区

Class Person
{
public:
    Person(int age, int height)
    {
        age = m_Age;
        m_Height = new int(height);
        cout<<"有参构造函数调用"<<endl;
    }
    ~Person()
    {
        if(m_Height!=NULL)
        {
            delete m_Height;
            m_Height = NULL;
        }
        cout<<"析构函数调用"<<endl;
    }
    int m_Age;
    int *m_Height;
};
test01()
{
    Person p1(18, 160);
    Person p2(p1);
    cout << "年龄:" << p1.m_Age << ",身高:" << *p1.m_Height<<endl;
    cout << "年龄:" << p2.m_Age << ",身高:" << *p2.m_Height<<endl;
    //会出错
}

出错原因:浅拷贝只拷贝地址,而当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<<p.m_A<<endl;
    Person p2;
    p2.m_A = 200;
    //200
    cout<<p.m_A<<endl;
        
}

静态成员变量有两种访问方式

//1. 通过对象进行访问
Person p;
cout<<p.m_A<<endl;
//2. 通过类名进行访问
cout<<Person::m_A<<endl;
静态成员函数
  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量

静态成员函数也有两种访问方式

void test()
{
    Person p;
    p.func();
    Person::func();
}

只能访问静态成员变量

class Person
{
public:
	static void func()
	{
        m_A = 100; 静态成员函数 可以访问 静态成员变量
        //m_B = 200; //静态成员函数 不可以访问 非静态成员变量,因为无法区分到底是哪个对象的变量
        cout<<"stataic void func调用"<<endl;
    }
	static int m_A;//静态成员变量
	int m_B;//非静态成员变量

	//静态成员函数也是有访问权限的
private:
	static void func2()
	{
        cout<<"static void func2调用"<<endl;
    }

};
成员变量和成员函数分开存储
class Person
{

};
void test
{
    Person p;
    //空对象占用内存空间为:1
    //C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
    //每个空对象也应该有一个独一无二的内存地址
    cout<<"size of p = "<<sizeof(p)<<endl;
}
class Person
{
	int m_A; //非静态成员变量 属于类的对象上
	static int m_B; //静态成员变量 不属于类的对象上
	void func(){} //非静态成员函数 不属于类对象上
	static void func2(){} //静态成员函数,属于类的对象上
};
void test
{
    Person p;
    //对象占用内存空间为:4
    cout<<"size of p = "<<sizeof(p)<<endl;
}

只有非静态成员变量属于类的对象上

this指针

this指针指向 被调用的成员函数 所属的对象

用途1. 解决名称冲突

class Person
{
	Person(int age)
	{
        this->age = age;
    }
	int age;
};
void test01()
{
    Person p1(18);
    cout<<"p1年龄:"<<p1.age<<endl; 
}

用途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年龄:"<<p2.age<<endl; 
}
空指针访问成员函数
class Person
{
public:
	void showClassName()
	{
        cout<<"this is Person class"<<endl;
    }
	void showPersonAge()
	{
        //报错原因是因为传入的指针是为NULL
        if(this==NULL)
        {
            return;
        }
        cout<<"age = "<<m_Age<<endl;
    }
	int m_Age;
};
void test01()
{
    Person* p = NULL;
    p->showClassName();
    p->showPersonAge();// 该行报错
}
const修饰成员函数

常函数:

  • 成员函数后加const后我们称为这个函数为常函数
  • 常函数不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

  • 声明常对象前加const称该对象为常对象
  • 常对象只能调用常函数
//常函数
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(); //常对象 不可以调用普通成员函数,因为普通成员函数可以修改属性
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值