目录
一、面向对象面向过程的初步认识
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,并用函数调用初步解决问题。
例如:
拿盆子 -> 放水 -> 放衣服 -> 放洗衣粉 -> 手搓 -> 换水 -> 手搓 - > 拧干 -> 晾衣服
这些是关注于过程的思想。
C++是基于面向对象的,关注的是对象,将一件事拆分成多个对象,并用对象之间的交互来完成。
例如:
洗衣服总共有四个对象:人、衣服、洗衣粉、洗衣机。
整个过程是通过人、衣服、洗衣粉、洗衣机之间的交互来完成的,人不需要关心洗衣机是怎样完成洗衣服的。
二、类的引入
在C++发展的时候,为了弥补C的结构体的不足,C++对C的结构体做了扩展。原本C语言的结构体中只能放成员变量的声明,但是为了满足面向对象的思想,如果一个对象只有成员变量的话是不足以完成对象之间的交互来完成任务的。
例如:
可以看出,C语言的结构体只有成员变量是不能够完成上面的系统的。
C++对struct的扩充如下:
C语言写一个栈的时候:
C++补充struct后写栈:
#include <iostream>
typedef int STDataType;
struct Stack
{
//有了stack这类里面就不用再取StackInit了
void Init(int N = 4)
{
//...
}
void Push(STDataType x)
{
//...
}
void Destroy()
{
//...
}
//...
STDataType* a;
int top;
int capacity;
};
int main()
{
//C++后可以这样定义
Stack st;
st.Init(10);
st.Push(1);
st.Push(2);
st.Push(3);
st.Destroy();
return 0;
}
C++把struct定义的变量变成了一个域,称为类域,里面不仅有成员变量,还有方法。
注意:C++的struct同时也支持C的使用(只有成员变量)。
上面结构体的定义,在C++中更喜欢用class来代替。
三、类的定义
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分 号不能省略。
//className是类的名字
class className
{
//类体:由成员函数和成员变量组成
};//注意整个";"
类定义方式一:
声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
//人
class Person
{
public:
//信息初始化
void Init(char* name,char* sex,int age)
{
//"_"是为了区分成员变量和传过来的参数;
_name = name;
_sex = sex;
_age = age;
}
//打印信息
void showInfo()
{
cout << _name << "-" << _sex << "-" << _age << endl;
}
public:
char* _name;//名字
char* _sex;//性别
int _age;//年龄
};
类定义的方式二:
类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
注意:在工程比较大的时候推荐声明和定义分离
四、类的访问限定符及封装
访问限定符
【访问限定符说明】
- public修饰的成员在类外可以直接被访问。
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)。
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)。
现阶段(类和对象上篇)可以把protected和private看作是一样的。
//人
class Person
{
public:
//信息初始化
void Init();
//打印信息
void showInfo();
private:
char* _name;//名字
char* _sex;//性别
int _age;//年龄
};
//此处需要指定showInfo是Person这个类域
void Person::showInfo()
{
//...
}
int main()
{
Person ps;
ps.Init();
ps.showInfo();
ps._name;//代码出错,因为_name参数为类的私有部分,不能访问
return 0;
}
封装
C语言在定义结构体和函数的时候是比较自由的,函数和结构体属性变量是分开的。
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackDestroy(ST* ps);
//...
C++对函数和属性封装在了一起,起到了很好的保护作用
typedef int STDataType;
//struct Stack
class Stack
{
public:
void StackInit(int N = 4)
{
//...
}
void StackPush(STDataType x)
{
//...
}
void StackDestroy()
{
//...
}
//...
private:
STDataType* a;
int top;
int capacity;
};
封装的重要性:
举个例子:陕西的兵马俑,在还没有保护时,谁都可以近距离接触兵马俑的话会对兵马俑造成不可逆的破坏,如果得到了保护后,例如,进去前需要买门票,再雇几个保安检查,游客只能按照规定参观,能得到很有效的保护。
五、类的实例化
int main()
{
Person._age = 100; // 编译失败:error C2059: 语法错误:“.”
return 0;
}
Person类是没有空间的,只有Person类实例化出的对象才有具体的年龄。
正确的玩法:
int main()
{
Person ps;
ps._age = 100;
return 0;
}
打个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出里面需要什么东西,没有实体的建筑,实例化出的对象 才能实际存储数据,占用物理空间。
六、类的对象计算大小
在大佬设计C++的时候,创建类的对象时应该怎么存类里面的成员函数和成员变量的呢。
当时大佬设计了三种方案:
第一种
类里面成员变量和成员函数都一起保存。
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一 个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。
第二种
保留成员变量和成员函数的地址,成员函数保存到了内存中的常态区,这样每次调用成员函数的时候就通过保存的这块地址访问常态区的成员函数。
缺点:虽然保存一个地址占的空间并不大,但是始终还有要保存多一个地址,完全没有必要。
第三种
只保留了成员变量,类的成员函数是存到了常态区,编译器编译的时候就能找到,节省了空间。
class A
{
public:
void func()
{
cout << _a << endl;
}
//private:
char _a;
int _b;
};
class A1
{
void func()
{
}
};
class A2
{
};
int main()
{ //创建对象
A aa1;
A1 a1;
A2 a2;
//结果为8,类的成员函数是存到了常态区,编译器编译的时候就能找到,建立对象只存成员变量。
//和算结构体的大小一样,需要对齐
cout << sizeof(aa1) << endl;
//结果为1,不存储有效数据,起到占位作用,标识对象存在
cout << sizeof(a1) << endl;
cout << sizeof(a2) << endl;
return 0;
}
运行代码:
8
1
1
结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐 注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
七、 this指针
1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
2. 只能在“成员函数”的内部使用。
看下面代码,想想这个问题:
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函 数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
class Date
{
public:
void Init(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.Init(2024, 4, 26);
d2.Init(2024, 4, 27);
d1.Print();
d2.Print();
return 0;
}
解析:
class Date
{
public:
void Init(int year, int month,int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << this << endl;
cout << _year << "-" << _month << "-" << _day << endl;
}
//void Print(&d1)
//void Print(&d2)
//
//编译器默认
//void Print(Date* this)
//{
// //this没必要写,因为没写编译器编译会自动加上,写了编译器就不加
// cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
//}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
d1.Init(2024, 4, 26);
d2.Init(2024, 4, 27);
d1.Print();
d2.Print();
cout << &d1 << endl;
cout << &d2 << endl;
//Date::Print();
//上面代码报错,没有东西传给Date* this,不知道用的是d1还是d2.
return 0;
}
运行结果:
009DFD8C
2024-4-26
009DFD78
2024-4-27
009DFD8C
009DFD78
我们打印了d1和d2还有调用Print函数时this的地址,发现两个地址都是一样的,更能说明C++编译器给每个“非静态的成员函数“增加了一个隐藏 的指针参数this,传的地址不一样,然后就能区分不同的对象。
但真正this指针是有const修饰的:
到这里能延申一个面试题:
最后,感谢您的阅读!!!