封装篇(上)
类和对象
类的定义:
class Dog
{
public:
char name[20];
int age;
int type;
void speak();
void run();
}
选择性暴露:封装
访问限定符
- public
- protected
- private
对象实例化
从栈中实例化
Dog dog;
Dog dog[20];
系统会自动回收
从堆中实例化
Dog *p = new Dog();
Dog *q = new Dog[20];
需要自行回收
对象成员的访问
栈访问
Dog dog;
dog.name = "哈哈";
dog.run();
return 0;
堆访问
Dog *p = new Dog();
p->name = "哈哈";
p->run();
delete p;
p = NULL;
return 0;
char * 的操作和注意事项
-
strlen(char* s): int
: 直到碰到第一个字符串结束符’\0’为止,然后返回计数器值strlen 与 sizeof 的区别,strlen 返回字符串长度,而 sizeof 返回 指针所占空间大小(一般为4)但是也可以是其他类型的传参。
-
strcat(char *s, char* s2): void
:将 s2 拼接到 s1 的尾部 -
strcat(char *s, int size, char* s2): void
strcat 函数的安全版,中间的参数为指定缓冲区大小 -
strcpy_s(char *s, int size, char *s2): void
:将后者字符串复制给前者 -
strcmp(char *s, char *s2): int
:串比较,看Asic码,str1>str2,返回值 > 0;两串相等,返回0 -
strncpy(char *s, int size, char *s2, int copyLen): void
:将 s2 的前 copyLen 个字符复制给 s1注意 size > copyLen;因为字符串数组最后一位为 '\0’
-
strncmp(char *s, char *s2, int cmpLen): int
:比较两个字符串的 前 cmpLen 个字符 -
strstr(char *s, char *s2):int
:判断 s2 是否是 s 的子串。如果是,则返回str2在str1中首次出现的地址;如果不是,则返回null;
字符串类型:string
初始化 string 对象的方式:
string s1; | s1为空串 |
---|---|
string s2(“ABC”); | 用字符串字面值初始化s2 |
string s3(s2); | 将s3初始化为s2的一个副本 |
string s4(n,‘c’); | 将s4初始化为字符’c’的n个副本 |
string 的常用操作:
s.empty() | 若s 为空串,返回 true,否则返回 false |
---|---|
s.size() | 返回s中字符的个数 |
s[n] | 返回s中位置为n的字符,位置从0开始 |
s1+s2 | 将两个串连接成新串,返回新生成的串 |
s1 = s2 | 把s1的内容替换成 s2 的副本 |
s1 == s2 | 判定相等 |
s1 != s2 | 判定不等 |
string s6 = "hello" + "world"; 非法
类内定义与内联函数
类内定义
将成员函数的函数体都在类的内部定义,则为类内定义
类内定义的成员函数,编译器会自动添加 inline 关键字尽量成为内联函数
类外定义
-
同文件类外定义
成员函数虽然在类的外面,但是其类的定义和函数定义都在同一个文件中
class Car { public: void run(); void stop(); void changeSpeed(); }; void Car::run() {} void Car::stop() {} void Car::changeSpeed() {}
-
分文件类外定义
Car.h
class Car { public: void run(); void stop(); void changeSpeed(); }
Car.cpp
#include "Car.h" void Car::run() {} void Car::stop() {} void Car::changeSpeed() {}
对象结构
内存分区
-
栈区:int x = 0; int *p = NULL;
特点:内存由系统进行控制
-
堆区:int *p = new int[20];
特点:必须使用 delete 来回收
-
全局区:存储全局变量及静态变量
-
常量区:存储字符串以及常量
-
代码区:存储逻辑代码的二进制
对象初始化
构造函数
- 在对象实例化时被自动调用,且仅被调用一次
- 构造函数与类同名
- 构造函数没有返回值
- 构造函数可以有多个重载形式
- 当用户没有定义构造函数时,编译器自动生成
构造函数可以设置默认值;但是注意:有参构造函数全部有默认值的话会和无参构造函数冲突导致编译无法通过
默认构造函数
不需要传递参数的构造函数
一个类可以没有默认构造函数,有别的构造函数也可以实例化对象
构造函数初始化列表
初始化列表特性:
- 初始化列表先于构造函数执行
- 初始化列表只能用于构造函数
- 初始化列表可以同时初始化多个数据成员
- 初始化列表更快
初始化列表存在的必要性:
class Circle
{
public:
Circle():m_dPi(3.14) {}
private:
const double m_dPi;
}
如果将 m_dPi 的赋值放在构造函数中执行,将会报错,因为它是常量。
拷贝构造函数
class Student
{
public:
Student()
{ cout << "Student" << endl; }
}
Student stu1;
Student stu2 = stu1;
Student stu3(stu1);
// 打印 Student 一次
定义格式: 类名(const 类名& 变量名)
class Student
{
public:
Student(const Student& stu){}
}
拷贝构造函数性质:
- 如果没有定义的拷贝构造函数则系统自动生成一个默认的拷贝构造函数
- 当采用直接初始化或复制初始化实例化对象时,系统自动调用拷贝构造函数
- 拷贝构造函数的参数是确定的,不能重载
传参时也会执行拷贝构造函数
class Student {
public:
Student() {
cout << "Student" << endl;
}
Student(const Student& stu) {
cout << "拷贝构造函数" << endl;
}
};
void test(Student s) {
}
int main() {
Student stu1;
Student stu2 = stu1;
Student stu3(stu1);
test(stu1);
system("pause");
return 0;
}
// Student
// 拷贝构造函数 * 3
// 这是因为 test 函数的传参为 Student 对象,触发了拷贝构造函数
析构函数
定义格式: ~类名()
析构函数不能有参数
析构函数存在的意义:释放资源
class Student
{
public:
Student() {
m_pName = new char[20];
}
~Student() {
delete []m_pName;
}
private:
char *m_pName;
}
析构函数性质:
- 如果没有自定义的析构函数则系统自动生成
- 析构函数在对象销毁时自动调用
- 析构函数没有返回值,没有参数,不能重载