#include<iostream>
using namespace std;
//一个类
class tmp
{
public:
//成员函数/方法
void func()
{
cout << "func()" << endl;
cout << _a << endl;
}
private:
//成员变量/属性
int _a;
char* _b;
};//注意分号class为定义类的关键字,tmp为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员。_或者m开头,注意C++中这个并不是强制的,只是一些惯例。struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,最明显的变化是C++的struct中可以定义函数,一般情况下我们还是推荐用class定义类。#include<iostream>
using namespace std;
struct fhvyxyci
{
int tmp;
//C++ struct中可以定义函数
void Print()
{
cout << tmp;
}
}f1;
int main()
{
f1.tmp = 10;
f1.Print();
return 0;
}这个代码可以正常输出10。
另外,C++对struct本身也进行了升级:
//C语言写法
typedef int Datatype;
typedef struct ListNode
{
Datatype data;
struct ListNode* next;
};//C++写法
typedef int Datatype;
struct ListNode
{
Datatype data;
ListNode* next;
};可以发现,C++中struct变量的类型都不用带struct关键字,不需要使用typedef关键字,并且在结构体中使用结构体本身的类型时也不需要。
inline内联函数。C++一种实现封装的方式,用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
public修饰的成员在类外可以直接被访问,protected和private修饰的成员在类外不能直接被访问,protected和private目前可以看做是一样的,以后在继承部分才能体现出它们的区别。}即类结束。class定义成员没有被访问限定符修饰时默认为private,struct默认为public。private/protected**,需要给类外使用的成员函数会放为**public**。#include<iostream>
using namespace std;
class fhvyxyci
{
public:
size_t size()
{
return _size;
}
size_t capacity()
{
return _capacity;
}
private:
size_t _size;
protected:
size_t _capacity;
};
int main()
{
//创建一个 class fhvyxyci类型的变量 f1
fhvyxyci f1;
//类的成员访问符和 struct 类型是一样的,访问成员函数时,即使没有参数也要带上括号
cout << f1.size() << endl;
cout << f1.capacity() << endl;
//不可以访问private成员
//cout << f1._size << endl;
//不可以访问protected成员
//cout << f1._capacity << endl;
return 0;
}当然,要注意由于还没有写构造函数(本文不涉及),所以这里的成员变量都没有初始化,都是随机值,当然你也可以先写一个函数进行赋值。
//加在类的 public限定符后
void Init(size_t s1, size_t s2)
{
_size = s1;
_capacity = s2;
}int main()
{
fhvyxyci f1;
f1.Init(1, 2);
cout << f1.size() << endl;
cout << f1.capacity() << endl;
return 0;
}这样这个代码就可以输出有意义的数值1和2了。
尽管这样可以达成我们的目标(对成员变量进行初始化),但是这和C语言相比,并不能体现出C++的优势,只有使用构造函数,才能体会到C++的精妙之处(请关注我的下篇博客)。
类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用作用域操作符指明成员属于哪个类域。
#include<iostream>
class fhvyxyci
{
public:
void Init(size_t s1, size_t s2);
private:
size_t _size;
size_t _capacity;
};
//声明与定义分离需要指明类域(和命名空间域一样),这种写法也适用于声明与定义在不同的文件中
void fhvyxyci::Init(size_t s1, size_t s2)
{
_size = s1;
_capacity = s2;
}类域影响的是编译的查找规则,上面程序中Init如果不指定类域fhvyxyci,那么编译器就把Init当成全局函数,那么编译时,找不到_size等成员的声明/定义在哪里,就会报错。指定类域fhvyxyci,就是知道Init是成员函数,当前域找不到的_size等成员,就会到类域中去查找。
用类类型在物理内存中创建对象的过程,称为类实例化出对象。
fhvyxyci f1;
//实例化过程类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间。
一个类可以实例化出多个对象,实例化出的对象才会占用实际的物理空间,存储类成员变量。
打个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,设计图规划了有多少个房间,房间大小功能等,但是并没有实体的建筑存在,也不能住人,用设计图修建出房子,房子才能住人。
类就像设计图一样,不能存储数据,实例化出的对象分配物理内存存储数据。

当定义出来这样一个类之后,不进行实例化时,它是不会占用空间的(代码本身除外)。
#include<iostream>
using namespace std;
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;
};而在main函数中创建一个Date类型的变量,也就是实例化出来,就会占用空间了。
int main()
{
Date d1;
return 0;
}首先分析一下类对象中有哪些成员。
类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么成员函数是否也被包含了呢?
首先函数被编译后是一段指令,对象中没办法存储,这些指令存储在一个单独的区域(代码段),那么就算对象中要存储成员函数,也只能是存储了成员函数的指针。
但是,对象中是否有存储指针的必要呢?
Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量year/_month/_day存储各自的数据,但是d1和d2的成员函数Init/Print指针却是一样的,存储在对象中就浪费了。如果用Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了,所以并不会存储函数指针,当需要调用成员函数时,会直接通过类找到函数。
另外,函数指针是一个地址,调用函数会被编译成汇编指令call 地址,并且编译器在编译链接时,就要找到函数的地址,而不是在运行时找。只有动态多态是在运行时找,所以需要存储函数地址,这个以后的博客会讲到。
综上,我们分析出对象中只存储成员变量,此外,C++规定类实例化的对象也要符合内存对齐的规则,并且和struct是一样的。
更多关于对齐规则的请在C语言结构体这篇博客的第二章获取,这里不再赘述了。
练习:A/B/C实例化的对象分别是多大?
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
class B
{
public:
void Print()
{
//...
}
};
class C
{};
int main()
{
A a;
B b;
C c;
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
return 0;
}A的大小是8,因为存在最小对齐数。
但是B/C的大小是多少呢?
上面的程序运行后,我们看到没有成员变量的B和C类对象的大小是1,为什么没有成员变量还要给1个字节呢?
因为如果一个字节都不给,怎么表示对象存在?所以这里给1字节,纯粹是为了占位标识对象存在。
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当实例化对象d1调用Init和Print函数时,函数是如何知道应该访问的是d1对象还是d2对象呢?
C++使用了一个隐含的this指针解决这个问题。
编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this指针。比如Date类的Init的真实原型为,void Init(Date*const this,int year,int month,int day)。
注:const在*右边,表示this指针的指向不能修改。
类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给成员变量_year赋值 _year=year,实际上是this->_year = year;
C++规定不能在实参和形参的位置显示的写**this**指针(编译时编译器会处理),但是可以在函数体内显式使用**this**指针。
#include<iostream>
using namespace std;
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//这个函数的参数是不合法的,不能显式写出this指针
//void Init(Date* this, int year, int month, int day)
//{
// //这里的this指针显式解引用是合法的
// this->_year = year;
// this->_month = month;
// this->_day = day;
//}
void test()
{
//对this指针的指向进行修改是不合法的
//this = nullptr;
}
void Print()
{
cout << _year << ":" << _month << ":" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}这个程序很显然是有错误的,p是一个空指针,但又对它进行了解引用,所以发生了空指针的解引用,程序会在运行时崩溃,选B。
真的是这样吗?
前面我们讲过,成员函数是不会存放在实例化对象中的,也就是**p->Print();**这一步压根没有对p进行解引用,而是通过类直接找到了成员函数并进行了调用。
所以这道题选择C。
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}这道题乍一看和上一道一样,都是通过空指针访问成员函数,所以这道题选择C?
这道题调用的成员函数中访问了成员变量,刚才我们说过,访问成员变量实际上是通过**this**指针进行的,也就是说,Print函数实际上是:
void Print()
{
cout << "A::Print()" << endl;
cout << this->_a << endl;
}这就体现出问题了,这里对**this**指针进行了解引用,但**this**指针也就是**p**,是一个空指针,所以这里发生了空指针的解引用,所以会在运行时出错,选B。
this指针是调用成员函数时进行的隐式传参,也就是一个函数形参,函数形参自然是存放在栈上的。选A。谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章!