类和对象,这是学习c++非常重要的一个章节。在学习的时候,前篇还算游刃有余,中篇最难,下篇轻舟已过万重山。
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成
c++是面对对象,但不是纯面对对象。面对对象就是随便一个地方都要写一个类。面对过程,关注解决问题的步骤和过程。
可以拿洗衣服来理解这两个东西。不要想着现在就把面向对象理解,你连面向对象的程序都没有学过。现在只需要有一点浅浅的理解,一定是随着学习慢慢深入的。
引入类,c++把结构体升级成了类。但是类和结构体的区别是什么?
用c++简单写一个栈,主要是为了看类和结构体的区别,具体怎么实现不重要。
struct Stack//结构体的定义,在C++中更喜欢用class来代替。
{
// 成员函数
void Init(int n = 4)//设定缺省参数
{
int* a = (int*)malloc(sizeof(int)* n);
if (a==NULL)
{
perror("malloc申请空间失败\n");
return;
}
capacity = n;
size = 0;
}
void Push(int x)
{
//...
a[size++] = x;
}
// 成员变量
int* a;
int size;
int capacity;
};
typedef struct ListNode
{
int val;
struct ListNode* next;//C语言要这样写
}LTN;
struct ListNode
{
int val;
ListNode* next;//ListNode就是类名
};
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
类的两种定义方式:
// 我们看看这个函数,是不是很僵硬?
class Date
{
public:
void Init(int year)
{
// 这里的year到底是成员变量,还是函数形参?
year = year;
}
private:
int year;
};
// 所以一般都建议这样
class Date
{
public:
void Init(int year)
{
_year = year;
}
private:
int _year;
};
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选
择性的将其接口提供给外部的用户使用。
访问限定符说明:
1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)
问题:C++中struct和class的区别是什么?
答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来
定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类
默认访问权限是private。
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用::作用域操作符指明成员属于哪个类域。
class Date
{
public:
void DatePrint();
private:
int _year;
int _month;
int _date;
};
// 这里需要指定是属于Date这个类域
void Date::DatePrint();
{
cout << _year << "年" << _ month << "月" << _date << "日" << endl;
}
类的实例化,是什么? 定义,用类类型创建对象的过程,称为类的实例化。
其实实例化就可以当作成开辟空间。类就像别墅的设计图,但是这个图纸是不能住人的,类的实例化就相当于建造完成别墅。
请问类对象的大小需要考虑什么?需要考虑成员函数的大小吗?需要考虑成员变量的大小吗?
先看下面的代码
class Date
{
public:
void Init(int year=2023, int month=1, int date=1)//设计缺省参数
{
_year = year;
_month = month;
_date = date;
}
private:
//成员变量
int _year;
int _month;
int _date;
};
int main()
{
Date d1;
Date d2;
d1.Init();
d2.Init();
return 0;
}
请问d1.year和d2.year是不是同一个空间,d1.init()和d2.init()是不是同一个函数?
其实是很好理解的,创建类对象的时候需要实例化,所以d1.year和d2.year肯定不是同一个空间。并且d1.init()和d2.init()肯定是同一个函数,显而易见如果不是同一个函数的话,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。函数是公用的,保存在代码段里。
所以在计算类的大小的时候只需要考虑成员变量的大小,当然要注意内存对齐
还有最后一个问题
// 类中仅有成员函数
class A2 {
public:
void f2() {}
};
// 类中什么都没有---空类
class A3
{};
难道类的大小为0吗?答案不是的,是1。空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
既然d1.init()和d2.init()是同一个函数。那我现在肯定有个疑问。函数里面的_year, _month, _date是指哪里的空间呢?为什么你是那里的。ok,如果是我们只靠上面的知识是不足以解释的,这里需要学一个新东西,this指针。
我们编译器,在调用函数时编译器会偷偷对函数进行处理,但是我们自己不能去操作。
class Date
{
public://这样就可以找到具体哪块空间了
void Init(Date* this,int year=2023, int month=1, int date=1)//设计缺省参数
{
this->_year = year;
this->_month = month;
this->_date = date;
}
private:
//成员变量
int _year;
int _month;
int _date;
};
int main()
{
Date d1;
Date d2;
d1.Init(&d1);
d2.Init(&d2);
return 0;
}
1. this指针存在哪里?
它时存在类里面吗,很显然根据我们从上面计算类的大小的结论来看,肯定不是。
它是隐含函数的形参,所以存放在栈里。
2.空指针问题
第一个例子
void func()
{
cout << "hello world" << endl;
}
Date* ptr=nullptr;
ptr->func();
可以正常运行吗?
结果正常运行。
第二个例子
ptr->Init(2022, 2, 2);
这个呢?
程序会崩溃。
这里说结论,不要看到箭头就以为要解引用。得看他需不需要解引用。看第一个例子,首先看函数在不在类里面的,要不要调用类里面的成员变量,不需要,所以不需要解引用。
再来看第二个例子。
void Init(Date* this,int year=2023, int month=1, int date=1)//设置缺省参数
{
this->_year = year;
this->_month = month;
this->_date = date;
}
很明显,程序崩溃在ptr传给this,再用this去解引用了。
那可不可以直接写成func()吗;不行?
1.会受到位域的限制
2.告诉你func是Date的成员函数,我们调用成员函数得传递this指针。