目录
1.面向过程和面向对象初步认识
2.类的引入
注意1:
注意2:
3.类的定义
4.访问限定符
5. 封装
6.类的作用域
(1)概念
(2)细节1:不同的类域是可以有同名函数的,并且跟函数重载没关系
(3)细节2:在类里面定义的函数默认是inline
补充知识点1:声明和定义的区分:开辟了空间就是定义!
6.类的实例化
7.类对象的存储方式
结构体内存对齐规则
8.this指针
(1)this是C++新增的关键字
(2)this指针的特性
(3)关于this指针的一个面试题
面试题1
补充知识点2:编译错误和运行崩溃区别
面试题2
面试题3
拿外卖来举例子,C语言面向过程看的是:点外卖,送外卖,拿外卖。C++面向对象看的是:商家,骑手,用户。
C++兼容C struct的用法
C++同时对struct进行了升级,把struct 升级成了类
1、结构体名称可以做类型
2、里面可以定义函数
struct Student
{
void Init(const char* name, const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Print()
{
cout << _name << " " << _gender << " " << _age << endl;
}
// 这里并不是必须加_
// 习惯加这个,用来标识成员变量
char _name[20];
char _gender[3];
int _age;
};
int main()
{
struct Student s1;
Student s2;
s1.Init("张三", "男", 18);
s1.Print();
return 0;
}
struct Student就是一个类,类是一个整体,无论成员变量放在前中后,成员函数都可以都可以使用
成员变量加_的目的,比如char _name[20]:为了和形式参数区分,如果不加_区分,当成员函数执行时,就近原则,就会把name,gender这些认为是形参而不是成员变量,运行就会错误
正确:
void Init(const char* name, const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
错误:
void Init(const char* name, const char* gender, int age)
{
strcpy(name, name);
strcpy(gender, gender);
age = age;
}
class Student //类
{
void Init(const char* name, const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Print()
{
cout << _name << " " << _gender << " " << _age << endl;
}
// 这里并不是必须加_
// 习惯加这个,用来标识成员变量
char _name[20];
char _gender[3];
int _age;
};
int main()
{
Student s2; //对象
s1.Init("张三", "男", 18);
s1.Print();
return 0;
}
class className
{
类体:由成员函数和成员变量组成
}; 一定要注意后面的分号
private私有 是防止你在类外面用对象直接访问:拿栈举例子
class Stack
{
public:
void Init();
void Push(int x);
int Top();
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
st1._top; 这样就是从类外面访问,错误!
}
当类定义在stack.h文件中:
class Stack
{
public:
void Init();
void Push(int x);
int Top();
private:
int* _a;
int _top;
int _capacity;
};
.cpp文件中访问
#include"stack.h"
void Stack::Init()
{
_a = nullptr;
_top = 0;
_capacity = 0;
}
// struct 不加访问限定符,默认是public
// class 不加访问限定符,默认是private
class Student
{
public:
// 类体:由成员函数和成员变量组成
void Init(const char* name, const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Print()
{
cout << _name << " " << _gender << " " << _age << endl;
}
private:
char _name[20];
char _gender[3];
protected:
int _age;
};
int main()
{
Student s2;
s2.Init("张三", "男", 18);
s2.Print();
//cout << s2._name << endl; 类外面无法访问private修饰的类的成员变量
//cout << s2._age << endl; 类外面无法访问protected修饰的类的成员变量
return 0;
}
封装:更严格管理设计
1、数据和方法封装到一起,类里面
2、想给你自由访问的设计成共有,不想给你直接访问的设计成私有
一般情况设计类,成员数据都是私有或者保护,想给使用者访问的函数是共有,不想给使用者访问时私有或保护
举例:
class Stack
{
private:
void Checkcapaicty() //增容接口不想让使用者访问也可以设为私有
{}
public:
void Init()
{}
void Push(int x)
{}
int Top()
{}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st;
st.Init();
st.Push(1);
st.Push(2);
st.Push(3);
st.Push(4);
cout << st.Top() << endl;
//cout << st._a[st._top] << endl;
return 0;
}
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
cout<<_name<<" "_gender<<" "<<_age<
两个类域
class Stack
{
public:
void Push(int x)
{}
};
class Queue
{
public:
void Push(int x)
{}
};
class Stack
{
public:
// 在类里面定义
// 在类里面定义的函数默认是inline
void Init()
{
_a = nullptr;
_top = 0;
_capacity = 0;
}
// 在类里面声明, 在.cpp里面定义
void Push(int x);
void Pop();
// 总结一下:实际中,一般情况下,短小函数可以直接在类里面定义,长一点函数声明和定义分离
private:
// 声明
int* _a;
int _top;
int _capacity;
};
总结:实际中,一般情况下,短小函数可以直接在类里面定义,长一点函数声明和定义分离
————————————————————————————手动分割符————————
类中的成员变量也是声明
private:
// 声明
int* _a;
int _top;
int _capacity;
类中的成员变量的定义在哪?:在创建对象的时候,开辟了成员变量的空间,所以属于定义(此时st1内部的值未初始化都是随机值)
int main()
{
Stack st1;
}
————————————————————————————手动分割符————————
①类(或类的对象)的大小不包括成员函数,因为成员函数存放在公共的代码段
②类大小不包括静态成员变量,静态成员变量在静态区
③类大小不包括内部类,内部类详情请见博客:(129条消息) 友元的详解_beyond.myself的博客-CSDN博客
所以正常按内存对齐计算就行
举个常规例子:
过程:_a 自身大小4字节,默认对齐数是8, 4 和 8取较小值4作为对齐数,则需要从对齐到4的整数倍的空间开始,占内存的0~3个字节,_ch 自身大小1字节,1 和 8取较小值1作为对齐数,则需要从对齐到1的整数倍的空间开始,占内存的第4个字节,一共5个字节,内存对齐:最大对齐数是三个对齐数1 4中的4,则结构体的总大小是4的倍数,则让 5 再浪费三字节空间到了偏移量 8 的位置,总共8字节空间
例子2:没有成员变量的类对象多大?空类多大?——都是1字节
因为:没有成员变量的类对象,编译会给他们分配1byte占位,表示对象存在过
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
/*void Print(Date* const this)
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}*/
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
/*void Init(Date* const this, int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}*/
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1;
Date d2;
d1.Init(2022, 5, 11);
d2.Init(2022, 5, 12);
//d1.Init(&d1, 2022, 5, 15);
//d2.Init(&d2, 2022, 5, 20);
d1.Print();
d2.Print();
//d1.Print(&d1);
//d2.Print(&d2);
return 0;
}
调用时this指针是隐含的,d1调用Print函数时,传参传过去就是d1的地址,Print函数接收时利用隐含的this指针接收了d1的地址,内部的成员变量前面默认都是this指向的,比如_year相当于 this->_year,
当然①类里面可以显示的把访问写出来,即:可以自己写上 this-> 。(不写编译器也会自己加上,所以我们一般都不加this->)
②也可以在类中打印this,即:cout<< this <
③但是不可以在实参和形参位置显示的写出this,即: void Print() 不允许写成 void Print(Date* this) ,不允许在形参加上this
④//this =nullptr; 是错误的: this指针本身不能修改,因为他是const修饰的
// this指向对象可以被修改 this->_year=year;
d1.Print(); //d1.Print(&d1);
d2.Print(); //d2.Print(&d2);
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
Print加上this指针后就是如下:
//void Print(Date* this)
//{
// cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
//}
Date d1;
d1.Init(2022, 5, 11); //d1.Init(&d1, 2022, 5, 15);
类里面成员函数:
void Init(int year, int month, int day)
//void Init(Date* const this, int year, int month, int day)
2. 只能在“成员函数”的内部使用
【1】下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Show()
{
cout << this << endl; //打印空指针也没错
cout << "Show()" << endl;
}
//private:
int _a;
};
int main()
{
A* p = nullptr;
p->Show();
//如果是 p->_a; 就有错
}
————————————————————————————手动分割符————————
在编译成中间语言的时候就没通过,也就是语法有错误,简单的说,
就是"你说的话,人家完全听不懂,没法帮你编译"所以是编译错误,
这个是原则性的错误.这个图就是语法错误!!
而编译通过了,但是运行时错误,说明编程人员对代码的思想有错误,
简单的说就是"你说的话,人家听得懂,但是不明白你什么意识",语无伦次的
让对方不知道你想做什么,就运行时错误了.
————————————————————————————手动分割符————————
选C、正常运行。首先一定不能选A,因为野指针问题都是运行时崩溃,编译器并不能自己检查出来
尽管p是空指针,但空指针是实参,把空指针实参传给形参是没错的,并且p-> Show() ,Show()成员函数放在公共的代码区,编译时去公共的代码区找到函数Show(),转换成 “call(函数的地址)”,就是普通的函数调用,cal1 A: :Show ( 0BA14D8h),并没有解引用空指针
p->_a; 类里面只存成员变量,访问了空指针的成员变量,解引用空指针会报错,所以错误,但是不会崩溃,编译器优化会忽略这个代码,因为什么事都没做。如果是访问修改这个代码就会报错: p->_a=0;
【2】下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
}
选B、运行崩溃 ,访问了空指针的成员变量,会解引用空指针,运行崩溃,但语法还是对的,所以不是A是B。