C++学习之旅第一站:类和对象基础_对象

一.类的定义与对象的创建

(一).类

I.定义

     简单来说,类是相较于C语言中的“​结构体​“”而言更为全面的数据类型声明,并不占用内存空间,只有在“​类的实例化(通过类名创建对象)​”过程中才会需要内存空间,其区别在于结构体中仅包括“​成员变量​”(​基本类型变量或者是结构体类型变量​),而类中不仅可以包含“​成员变量​”,也可以包含对成员变量进行操作的“​函数​”

  ​实例如下:

#include<stdio.h>
//C语言中的结构体
struct Student{
//成员变量
int number;// 排名
char *name;//学生姓名
int grate; //学生成绩
};
//输出函数
void Printf(struct Student ud){
printf("排名为%d的同学是%s,他的成绩是%d",ud.number,ud.name,ud.grate);
}
int main(){
struct Student ud; //定义一个类型struct Student变量ud
ud.number=3; //成员变量的初始化
ud.name="小明";
ud.grate=96;
Printf(ud); //函数调用
}

II.类结构定义

关键字 ​class​ 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表

C++学习之旅第一站:类和对象基础_对象_02

III.类成员访问方式

   方法一:通过“ . ”访问

创建对象后,可以通过“ . ”来进行访问成员

实例如下:

int main(){
//通过类来定义变量,即创建对象
class Student stu1; //也可以省略关键字class
//为类的成员变量赋值
ud.number = 3;
ud.name = "小明";
ud.score = 96;
//调用类的成员函数
ud.Print();
return 0;
}
方法二:通过“->”(对象指针)访问

对象指针:就是指向对象的一个指针,通过指针对成员进行操作

实例如下:

  (1)在栈(Stack)上创建对象
int main(){
//通过类来定义变量,即创建对象
class Student stu1; //也可以省略关键字class
class Student *ud=&stu1;
//为类的成员变量赋值
ud->number = 3;
ud->name = "小明";
ud->score = 96;
//调用类的成员函数
ud->Print();
return 0;
}
(2)在堆(Heap)上创建变量
int main(){
//通过类来定义变量,即创建对象
class Student *ud=new Student;
//为类的成员变量赋值
ud->number = 3;
ud->name = "小明";
ud->score = 96;
//调用类的成员函数
ud->Print();
delete ud; //一定要记得删除,释放空间,防止堆积
return 0;
}
(3)通过引用符来操作
#include<iostream>
using namespace std;
class Studnet{
public:
int age;
int grate;
};
int main(){
Student td;
Student &r=td;
r.age=12;
r.grate=90;
return 0;
}
(4)堆和栈存储区别

I.在栈上创建的对象是有名字的,不用指针指向也可以找到它;但是在堆上创建的对象是没有名字的,我们需要通过指针来指向它,才能访问其成员变量和成员函数

II.栈内存是程序自动管理的,不能使用 delete 删除在栈上创建的对象;堆内存由程序员管理,对象使用完毕后可以通过 delete 删除.

IV.类成员访问范围

(1)在类的成员函数内部,能够访问:

      -当前对象的全部属性、函数;

-同类其他对象的全部属性、函数


(2)在类的成员函数以外的地方,只能够访问该类对象的公有成员

(二).对象(object)

         在C语言中可以通过结构体名来定义变量,在 C++ 中可以通过类名来定义变量。不同的是,通过结构体定义出来的变量还是叫变量,而通过类定义出来的变量有了新的名称,叫做对象(Object)

实际例子如下:

#include <stdio.h>
//通过class关键字类定义类
class Student{
public:
//类包含的变量
char *name; //学生姓名
int number; //学生名次
int score; //学生成绩
//类包含的函数
void Print(){
printf("排名为%d的同学是%s,他的成绩是%d",number,name,grate);
}
};
int main(){
//通过类来定义变量,即创建对象
class Student stu1; //也可以省略关键字class
//为类的成员变量赋值
ud.number = 3;
ud.name = "小明";
ud.score = 96;
//调用类的成员函数
ud.Print();
return 0;
}

在 C++ 中,通过类名就可以创建对象,这个过程叫做类的实例化,因此也称对象是类的一个实例

(Instance)。

有些资料也将类的成员变量称为属性(Property),将类的成员函数称为方法(Method),


在面向对象的编程语言中,经常把函数(Function)称为方法(Method)。

(三).数据类型图(回顾)

C++学习之旅第一站:类和对象基础_对象_03

八大基本数据结构:​https://www.php.cn/java/base/479128.html​​​


(四).拓展(new与malloc用法)

I.new用法

  (1)结构

//创建

(数据类型)*(变量名)= new (数据类型)[变量个数];

//销毁

 ​delete[]变量名;

  (2)具体使用演示
//例1
int *a=new int ;//申请单个变量空间
delete a; //销毁

//例2
int *a=new int[5];//申请多个连续的变量空间
delete[]a;//销毁

II.malloc的用法

(1)结构

//创建

(数据类型)*(变量名)=(数据类型 *)malloc(sizeof(数据类型)*(变量个数))

//销毁

  ​free(变量名);

(2)具体使用演示
int *a = (int *)malloc(sizeof(int)*5);//创建
free(a); //释放

注意:​new和delete配套使用,malloc与free配套使用


二.类的成员变量与成员函数

C++学习之旅第一站:类和对象基础_对象_04

(一)定义

      类简单来说为一种抽象的数据类型,普通数据类型仅包含“​成员变量​”,而类中既包含“​成员变量​”又包含“​成员函数​”


(1)类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存,定义类的时候不能对成员变量赋值,类是一种数据类型定义,不占用内存空间,只有在用类定义对象时才会占用空间

(2)类的成员函数也和普通函数一样,都有返回值和参数列表,它与一般函数的区别是:成员函数是一个类的成员,出现在类体中,成员函数的作用范围由类来决定;而普通函数是独立的,作用范围是全局的或在某个命名空间内。

疑问:成员函数占用内存空间吗?      

       成员函数也是函数,函数都是有地址的,所以函数是占用内存空间的,但函数由于是多对象共享的,所以函数不占用对象的内存地址,是放在代码段的

(二)成员函数

如果函数是在类中被定义,则为内联函数,反之,则不是

//方式1
#include<iostream>
using namespace std;
class Student{
public:
int number;
char *name;
int score;
public:
void Print();//函数声明
}
//函数定义
viod Student::Print(){
cout<<name<<"的名次为"<<number<<"成绩为"<<score<<endl;}
//方式2
#include<iostream>
using namespace std;
class Student{
public:
int number;
char *name;
int score;
public:
//内联函数
void Print(){
cout<<name<<"的名次为"<<number<<"成绩为"<<score<<endl;}
};
// 方式3(通过inline将外部定义函数变为内敛函数)
#include<iostream>
using namespace std;
class Student{
public:
int number;
char *name;
int score;
public:
void Print();//函数声明
}
//函数定义
inline viod Student::Print(){
cout<<name<<"的名次为"<<number<<"成绩为"<<score<<endl;}

"::"域操作符,连接类名和函数名

推荐用方式1(类外部定义,内部声明),是一个好习惯

注意:成员函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前。

(三)构造函数和析构函数

I.构造(Constructor)函数

定义:

成员函数的一种,名字和类名相同,可以有参数,不能有返回值​(​void也是不可以的​)

作用

对对象进行初始化

如果定义类时没有写构造函数,编译器会自动生成一个默认的无参构造函数,不做任何操作,

在定义对象时构造函数被自动调用,对象生成后,不能在使用构造函数


注意:​一个类可以有多个构造函数(参数类型或个数不同)

分类:

复制构造函数和类型构造函数

应用实例:

#include<iostream>
using namespace std;
class Complex { private:
double real, imag;
public:
//自定义的构造函数,要传入相应的参数才可以使用,i为可缺省参数
Complex(double r, double i = 0) {
cout << "初始化成功!";
};
};
int main() {
Complex c1(1);
return 0;
}
复制构造(copy constructor)函数

定义:

只有一个参数,即对同类对象的引用的构造函数

形式:

X::X(X&) 或 X::X(const X&)

如果未定义复制构造函数,则编译器生成默认的复制构造函数,如果已经自定义,则默认的便不存在

应用场景

场景一:当用一个对象去初始化另一个对象时

#include<iostream>
using namespace std;
class Student{
public:
int age;
int grate;
};
int main(){
Student td1;
Student td2(td1);//调用默认的复制构造函数
Studnet td3=td2;//初始化语句,非赋值语句
return 0;
}

场景二:如果某函数参数是类A的对象,该函数被调用时,类A的复制构造函数将被调用

#include<iostream>
using namespace std;
class Student {
public:
int grate;
int age;
public:
//构造函数
Student() {
cout << "Hello World!" << endl;
};
//复制构造函数
Student(Student& td) {
cout << "You're welcome!" << endl;
};
};
void Func(Student td) {
cout << "You are right!" << endl;
}
int main() {
Student td;//调用构造函数
Func(td);//调用复制构造函数
return 0;
}

运行结果:

C++学习之旅第一站:类和对象基础_类_05

场景三:如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调

#include<iostream>
using namespace std;
class Student {
public:
int age;
public:
Student(int n) { age = n; };
Student(const Student& td) {
age = td.age;
cout << "You are welcome!" << endl;
};
};
Student Func() {
Student td(5);
return td;
}
int main() {

cout << Func().age;
return 0;
}

运行结果如下:

C++学习之旅第一站:类和对象基础_对象_06

注意:​对象间赋值并不导致复制构造函数被调用

类型转换构造函数

定义:

只有一个参数,而且不是复制构造函数,一般就可以看作是转换构造函数

目的:


实现类型的自动转换

应用实例:

#include<iostream>
using namespace std;
class Student {
public:
int age;//学生年龄
int grate;//学生分数
public:
//类型转换构造函数
Student(int i) {
cout << "You're welcome!" << endl;
age = i, grate = 0;
}
Student(int a, int b) {
age = a, grate = b;
}
};
int main() {
Student td(3, 56);
Student ud = 12;//类似于ud=Student(12);
td = 23;//23被自动转换为一个临时的Student对象
cout << td.age << " " << td.grate << endl;
return 0;
}

显式类型装换构造函数:

在类型转换函数前加关键字explicit,便无法直接通过整数来自动转换为临时对象,必须通过构造函数格式

注意:​在对象间赋值时不会调用类型转换构造函数

运行结果:

C++学习之旅第一站:类和对象基础_类_07

II.析构(Destructors)函数

定义:

名字和类名相同,在前面加‘~’,没有参数和返回值,一个类最多只能有一个析构函数


析构函数对象消亡时自动被调用​,定义析构函数可用来做善后工作,比如释放分配的空间

如果未定义析构函数,编译器自动生成无参的析构函数,若已定义,编译器便不再生成


代码演示如下:

#include<iostream>
using namespace std;
class Student{
public:
int age;
public:
~Student(){
cout<<"You're welcome!"<<endl;
}
};
int main(){
Student td[3];
return 0;
}

运行结果:

C++学习之旅第一站:类和对象基础_对象_08

构造函数和析构函数实例:

#include<iostream>
using namespace std;
class Student {
public:
int age;
//构造函数
Student() { age = 9; };
//复制构造函数
Student(Student& td) {
cout << "You're welcome!\n";
}
//析构函数
~Student() {
cout << "Hello\n";
}
};
int main() {
Student td;
Student ud(td);
Student id[3];
return 0;
}

运行结果:

C++学习之旅第一站:类和对象基础_类_09

函数重载

构造函数之间的参数类型或参数个数不同

#include<iostream>
using namespace std;
class Student {
public:
int grate;
int age;
public:
//构造函数1
Student(int s1, int s2) {
grate = s1, age = s2;
};
//构造函数2
Student(int s3) {
grate = s3,age=0;
};
//构造函数3
Student() {
grate = 54, age = 23;
};
public:
//输出对象成员
void Print() {
cout << "grate=" << grate << " " << "age=" << age<<endl;
};
};
int main()
{
Student td1(2,3);//构造函数1初始化
td1.Print();
Student td2(2); //构造函数2初始化
td2.Print();
Student td3; //构造函数3初始化
td3.Print();
return 0;
}

三.类成员的访问权限及类的封装

C++学习之旅第一站:类和对象基础_对象_10

(一)类成员的访问限制

  • public:类的对象、友元、子类均能访问的成员。
  • protected:仅有子类能够通过类的成员函数方式进行访问,对于子类的对象也不可访问。对于本类的对象以及友元函数均不可访问。
  • private:仅本类的成员以及有缘可以访问,对于该类的对象不可访问。
  • “隐藏”,是设置私有成员的一种机制​,​目的是强制对成员变量的访问一定要通过成员函数进行,如果成员变量的属性更改后便于通过成员函数修改
  • 以上三种关键字出现的次数和先后次序没有限制没有上述关键字,则缺省地被认为是私有成员

如果某个成员前面

I.private和public访问权限问题

#include <iostream>
using namespace std;
//类的声明
class Student{
private: //私有的
char *m_name;
int m_age;
float m_score;
public: //共有的
void setname(char *name);
void setage(int age);
void setscore(float score);
void show();
};
//成员函数的定义
void Student::setname(char *name){
m_name = name;
}
void Student::setage(int age){
m_age = age;
}
void Student::setscore(float score){
m_score = score;
}
void Student::show(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
int main(){
//在栈上创建对象
Student stu;
stu.setname("小明");
stu.setage(15);
stu.setscore(92.5f);
stu.show();
//在堆上创建对象
Student *pstu = new Student;
pstu -> setname("李华");
pstu -> setage(16);
pstu -> setscore(96);
pstu -> show();
return 0;
}

private下的成员变量不能通过类定义的对象进行赋值,可以通过类内部

public下的成员函数来实现赋值

成员变量大多是以m_开头,是约定俗成的,为了和成员函数中的形参名字区分开

一般的,我们可以通过set()函数对privite下的成员变量进行赋值,通过get()函数读取成员变量的值

II.protected访问权限问题

只有在派生类中,派生类的成员变量才可以访问基类的成员变量

代码如下:

//基类
class Base
{
public:
Base();
private:
void foo();
protected:
int m;
};
//派生类
class Derive : public Base
{
public:
Derive();
};

Derive::Derive()
{
this->m = 1; // 这里访问m没有问题
}

(二).类的封装

I.定义

我们可以把程序按某种规则分成很多“块”,类与类之间可能会有联系,每个类都有一个可变部分(public)和一个不可变部分(private)。我们需要把可变部分和不可变部分分离出来,将不可变的部分暴露给其他类,而将可变的部分隐藏起来,以便于随时可以让它修改。这项工作就是封装。


通俗理解:每个人都有自己的不想让别人知道的小秘密,类也有,为了对这些内容进行限制,我们可以通过public和private来实现这个功能

private 关键字的作用在于更好地隐藏类的内部实现,该向外暴露的接口(能通过对象访问的成员)都声明为 public,不希望外部知道、或者只在类内部使用的、或者对外部没有影响的成员,都建议声明为 private。

根据C++软件设计规范,实际项目开发中的成员变量以及只在类内部使用的成员函数(只被成员函数调用的成员函数)都建议声明为 private,而只将允许通过对象调用的成员函数声明为 public。

声明为 private 的成员和声明为 public 的成员的次序任意,既可以先出现 private 部分,也可以先出现 public 部分。如果既不写 private 也不写 public,就默认为 private

补充:

I.引用操作符(“&”)的应用

       通俗理解为类似于一个别名,某个变量的引用,相当于给该变量起了个别名,如果使用别名,与直接使用该变量是一样的,如果通过别名来改变其值也是可以的。

格式:

类型名 &引用名=变量名

要求:

(1)定义引用时一定要初始化某个变量

(2)初始化后,就不能再引用其他变量(从一而终)

(3)只能引用变量,不能引用常量和表达式

注意​:​如果引用被常变量定义,则不可以通过引用来改变变量的值

操作实例:

//引用的操作实例之“交换两个数”(通过指针不是很美观)
#include<iostream>
using namespace std;
class Swap {
public:
int n1, n2;
public:
void print(int& n1, int& n2 ) {
int temp;
temp = n1;
n1 = n2;
n2 = temp;
}

};
int main() {
Swap st;
int m = 9;
const int n=0;
m = n;
st.n1 = 10;
st.n2 = 11;
st.print(st.n1, st.n2);
cout << "n1的值为:" << st.n1<<endl;
cout << "n2的值为:" << st.n2;

return 0;
}

运行结果为:

C++学习之旅第一站:类和对象基础_类_11

II.const关键字用法解析

用法一:定义常量

#include<iostream>
using namesapce std;
int main(){
const int a=9;
}

用法二:定义常量指针

(1)不可以通过常量指针修改其指向内容
#include<iostream>
using namespace std;
int main(){
int n,m=9;
int*p=&m;
*p=67;//错误用法
return 0;
}
(2)不能把常量指针赋值给非常量指针
int a=0,b=7;
const int *p=&a;
int *q=b;
p=q;//正确
q=p;//错误用法
q=(int*)p;//通过强制类型转化也可以实现
(3)指针参数为常量指针时,可以避免内部不小心修改指针所指向内容

用法三:定义常引用

注意:不能通过常引用修改其值