简述:自此就正式进入C++的内容学习了 , 对于C++的面向对象和C的面向过程,属笔者愚笨,尚不理解,但领悟后会第一时间分享给大家。类和对象的语法知识较为繁冗复杂,望学有所得。
目录
类的引用:
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:
之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数
首先让我们来回忆一下结构体
struct Student
{
char _name[20];
int _age;
}
int main()
{
struct Student s1;
s1._age = 1;
}
C++兼容C里面结构体的用法 同时在C++中升级为了类
C++中的类 不仅可以定义成员变量 还可以定义方法/函数 》》》
struct Student
{
//成员变量
char _name[20];
int _age;
//...
//成员方法
void Init(const char* name, int age)
{
strcpy(_name, name);
_age = age;
}
void Print()
{
cout << _name << endl;
cout << _age << endl;
}
};
类的定义
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
class classname
{
//类体
};
类有两种定义方式 :
第一种方式:将函数的定义和声明都放在类体中
注意:成员函数在类中定义,编译器有可能将其当成内联函数。
第二种方式:声明放在类的头文件中 , 定义放在.cpp文件中
注意:成员函数名前要加类名
访问限定符:
封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
简言之 就是把成员定义 和 成员函数(方法)一起放在了类里面
一般在定义类的时候 建议明确定义访问限定符
访问限定符
上面封装提到了访问权限 这个访问权限怎么提供呢? 就是通过访问限定符
访问限定符的分类:
默认访问限定符:
但是还是推荐自己设置访问限定符
访问限定符实际使用:
class Stack
{
public:
void Init()
{
_a = nullptr;
_top = _capacity = 0;
}
void Push(int x )
{
//...
}
int Top()
{
assert(_top > 0);
return _a[_top - 1];
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st;
st.Init();
st.Push(1);
int top = st.Top();
st._a = 0; //err
return 0;
}
由上可见实际中的private后的成员变量是没有访问权限的 而public后的成员方法则是可以访问的
访问限定符说明:
1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
类的作用域:
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::作用域操作符指明成员属于哪个类域。
class Stack
{
public:
void Test();//这里只有一个声明
private:
int* _a;
int _top;
int _capacity;
};
void Stack::Test()//类外面 对函数进行定义时 要知道去哪里去找
{
cout << _a << _top << _capacity << endl;
}
类对象模型
计算类对象的大小
计算类对象的大小 这里需要考虑的两种特殊情况:
1.类中仅有成员函数
2.空类
这里需要考虑 成员函数是否也占据对象的空间呢?
我们通过实例来观察
欸? 1 是什么?空类也有空间吗 有成员函数等同于空类吗?
那我们观察一下正常情况 》》》
我们这里可以看到计算的大小
是只有成员变量的大小 没有成员方法的大小 ,由此我们推出成员函数是没有存在对象中的
结合上面的例子:
每个对象中都有独立的成员变量 不同对象调用成员函数,调用的是同一个
静态成员函数和静态成员变量在编译时就已经在全局区分配的内存
内存对齐规则
类和结构体存储方式是一样的,具体可参考结构体内存对齐规则
https://blog.csdn.net/D_H964/article/details/126975339?spm=1001.2014.3001.5502
类的实例化:
用类类型创建对象的过程,称为类的实例化
类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没
有分配实际的内存空间来存储它;
一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
举个简单的例子: 》》》
类就像是我们土木的建筑施工图 , 有施工图有房子吗?
没有
而类的实例出对象的过程 就是土木人施工的过程 就是建造房子的过程
结合上面空类的大小为什么为1呢? 很明显就是实例化出来的
空类的大小会给1 但是这个1只是为了占位,表示对象存在 不存储有效数据
this指针
this指针的引用:
我们先来定义一个日期类
class Date
{
public:
void Info(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year <<"-" << _month <<"-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1, d2;
d1.Info(2022, 1, 11);
d2.Info(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
看了打印结果,我们产生了疑问??? 函数为什么能识别出来d1 \ d2呢
因为C++引用了this指针:
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”
的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
也就是实际上函数是这样的 》》》
this指针的特性:
1. 就有人好奇 我们可以自己传实参给this吗 不用编译器传
很显然是不可以的 : 调用成员函数时不能传实参给this
2.在定义形参的时候 我们可以自己声明 Data* this 吗
由此: 定义成员函数时,也不能声明形参this
3.那么在成员函数内部,我们可以自己使用this吗
当然是可以的: 在成员函数内部,我们可以显示使用this
4.this是存在哪的?
因为this是作为一个形参存在 一般情况下是在栈的 有些编译器会放到寄存器中 如vs2013 放到ecx中
编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(VC++编译器是放在ECX中,其它编译器有可能不同)。
补充面试题:
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0; //C
}
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA(); //B
return 0;
}
两道题的区别就是第一题没有解引用this 因此即使this为空也能正常运行
第二题则对空指针进行解引用,因此会程序崩溃
(这里均不涉及语法错误,虽然p是空指针,但是空指针调用函数成员函数不会编译报错, 因为空指针不是语法错误,编译器检查不出来)