- C语言是面向过程的语言,而C++是面向对象的语言,那么类与对象到底是什么呢?
1、什么是类?
C++的类和结构体与C语言的结构体差不多,不过C语言结构体中只能定义变量,而C++的结构体和类内不仅可以定义变量,也可以定义函数。
struct ListNode_C
//c语言的结构体
{
int _val;
struct ListNode_c* _next;
//只有加上struct才是一个类型
struct ListNode_c* _prev;
};
struct ListNode_CPP
//c++的结构体
{
int _val;
struct ListNode_CPP* _next;
ListNode_CPP* _prev;//定义类的时候,可以加struct也可以不加struct
//还可以定义成员函数
ListNode_CPP* CreatNode(int val);
};
2、类一般怎样定义?
class为关键字,classname为类的名字,{}中为类的主体还有{}后的分号‘;’。
class Person
{
public:
void ShowInfo()
{
cout << name << endl;
cout << age << endl;
}
void Show();
private:
char name[10];//10字节
int age;//4字节
int _capacity;//4字节
};
Person::Show()
{
cout<<_capacity<<endl;
}
(1)声明和定义都放在类体里,成员函数在类内定义,编译器可能会将其当成内联函数处理。
(2)声明放在.h文件里,类的定义放在.cpp文件中
3、类的访问限定符
(1)为什么会有访问限定符?/
访问限定符的作用 ?
类内的一些东西是选择性的提供给外部用户的,通过访问权限可选择性的将其接口提供给外部的用户使用。
(2)访问限定符有几种?分别什么作用?
public:修饰的成员在类外可以直接被访问
protected和private修饰的成员不可以直接在类外进行访问
(3)访问权限作用域从该访问限定符出现的位置开始到下一个访问限定符出现为止。
(4)class的默认访问权限为private,struct为public(struct要兼容C)
- 问题:
1、C++中的struct和class的异同分别是什么?
答:
相同点:C++需要兼容C语言,所以C++中的struct可以当作结构体去使用,且c++中的结构体也可以去定义类。
不同点:struct的成员默认访问方式是public,class的成员默认访问方式是private
2、谈谈面向对象的三大特性之一,封装。
答:封装是,将数据和操作方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
4、类的作用域和类的实例化
1、类定义了一个新的作用域,类的左右成员都在类的作用域中,在类外实现成员函数时,需要加上“类名::”在函数名之前。
2、类的实例化,用类类型创建对象的过程称为类的实例化。
比如:类实例化出的对象就像现实中使用建筑设计图造出的房子而类就像是设计图。
(1)类只是限定了有哪些成员,并没有分配实际的内存空间。就像建筑设计图只画出了建筑各个部分,但是并没有分配实际的场地去建造这栋楼。
(2)一个类可以实例化出多个对象,且对象占用实际物理空间,存储成员变量。就像一张建筑设计图可以建造多栋楼一样。
5、类对象模型
1、怎么计算类对象大小?
class Stack
{
public:
void Push(int x)
{
}
void Pop();
bool Empty();
int *_a;//四字节
int _size;//四字节
int _capacity;//四字节
};
int main()
{
Stack s1;
cout<<sizeof(s1)<<endl;//12字节
system("pause");
return 0;
}
//对象中只存储成员变量,不存储成员函数
可以发现求出的对象大小只是成员变量的大小而不包含成员函数的大小,为什么么呢?
答::每个对象中成员变量是不同的,但是调用同一份函数,每个对象中都保存一段相同的代码,浪费空间,所以将成员函数放在了公共代码段。
做如下测试:
//类中有成员变量和成员函数
class A1
{
public:
void f1();
private:
int _a;
};
//类中只有成员函数
class A2
{
public:
void f2(){};
};
//类中什么都没有
class A3
{};
int main()
{
A1 s1;
A2 s2;
A3 s3;
cout << sizeof(s1) << endl;//4
cout << sizeof(s2) << endl;//1
cout << sizeof(s3) << endl;//1
//没有成员变量或者空类,其大小为1,而不是0。
//不是为了存储数据,而是为了占位,表示对象存在。
system("pause");
return 0;
}
- 可见,类对象存储方式确实如上所述,那么为什么类中仅有成员函数或空类它的大小为1呢?
答:C++编译器不允许对象为零长度。
试想一个长度为0的对象在内存中怎么存放?怎么获取它的地址?为了避免这种情况,C++强制给这种类插入一个缺省成员,长度为1。如果有自定义的变量,变量将取代这个缺省成员。
不是为了存储数据,而是为了占位,表示对象存在。
2、类的内存对齐(结构体的内存对齐)
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
VS中默认的对齐数为8,gcc中的对齐数为4- 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
# include <stdio.h>
# include <stdlib.h>
//# pragma pack(1)
//# pragma pack(2) //设置默认对齐数为2
//# pragma pack(4)
//# pragma pack(8)
//# pragma pack(16)
struct s1 //结构体
{
char c1; //默认对齐数是8 对比4,选4 则3*4=12
int i; //设置对齐数为1, 1+4+1=6
char c2; //设置对齐数为2,2+4+2=8
};
struct s2
{
char c1; // 2*4=8
char c2;//设置对齐数为1, 1+1+4=6
int i; //设置对齐数为2,2+4=6
};
struct s3
{
double c1; //8*2=16
char c2;//设置对齐数为1, 8+1+4=13
int i; //设置对齐数为2,8+2+4=14
};
//#pragma pack() //取消设置的默认对齐数,还原为默认设置
struct s4
{
char c1; //struct s3的对齐数为8,则整体对齐数为8,8+16+8=32
struct s3 s3;//设置对齐数为1, 1+13+8=22
double d; //设置对齐数为2,2+14+8=24
};
struct s5
{
struct s4 s4; //同s4,32+3*8=56
int e;//设置对齐数为1, 22+4+5+4+4=39
char f[5];//设置对齐数为2,24+4+6+4+4=42
char* h;
void(*g)(int); //空类型指针函数
};
int main()
{
printf("s1=%d\n", sizeof(struct s1)); //输出结构体大小
printf("s2=%d\n", sizeof(struct s2));
printf("s3=%d\n", sizeof(struct s3));
printf("s4=%d\n", sizeof(struct s4));
printf("s5=%d\n", sizeof(struct s5));
system("pause");
return 0;
}
不设置对齐数、设置对齐数为1和设置对齐数为2的情况下的运行结果:
1、不设置对齐数
2、设置对齐数为1;
设置对齐数为2:
特别注意:结构体嵌套时,被嵌套的结构欧体对齐数为自己内部的最大对齐数。
问题:
- 结构体怎么对齐? 为什么要进行内存对齐?
答:
(1)
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8,gcc中的对齐数为4
- 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
(2)
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
- 如何让结构体按照指定的对齐参数进行对齐?
答:设置对齐参数可在结构体struct之前加上#pragma pack(对齐数),在struct之后加上#pragma pack;便可以设置对齐参数
- 如何知道结构体中某个成员相对于结构体起始位置的偏移量?
答:我们可以使用这个#define offsetof(s,a) (size_t) (((s*)0)->a),这个用法就是我们告诉编译器有一个指针指向结构体s,而它的值是0,然后我们取结构体中的a,a的地址就是a的偏移量了
或者将成员的地址和结构体的地址取出来,转换为char*类型,相减即可得到偏移量。
#include<iostream>
#include<cstdlib>
using namespace std;
#define offsetof(s,a) (size_t) &(((s*)0)->a)
struct a{
int m;
char n;
double c;
};
#pragma pack(4)
struct b{
char o;
double p;
int j;
};
int main()
{
cout << "a的内存大小为" << sizeof(a) << endl;
cout << "b的内存大小为" << sizeof(b) << endl;
cout << offsetof(b, p) << endl;
system("pause");
return 0;
}
运行结果:
- 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景。
答:
(1)
大端存储模式:就是内存的低地址上存着数据的高位,高地址上存着数据的低位。
小端存储模式:就是内存的低地址上存数据的低位,而高地址上存数据的高位。
(2)两种方法:取地址/联合体特性
#include<stdio.h>
void CheckSystem1()
{
int a = 1;
int num = (*(char*)&a);//&a 取出a的地址; (char*)&a 代表a变量地址的第一个字节的地址
printf("%d\n", num);//(*(char*)&a) 解引用取出第一个字节保存的内容打印
if (num == 1)
printf("小端\n");
else
printf("大端\n");
}
int main1()
{
CheckSystem1();
getchar();
return 0;
}
//2.联合体特性
int CheckSystem2()
{
union check
{
int num;
char a;//2个变量公用一块内存空间,并且2个变量的首地址相等
}b;
b.num = 1;//1存放在变量num的低位
return (b.a == 1);//当变量a=1,相当于将数据的低位存到了内存的低地址处,即小端模式
}
int main2()
{
int c = CheckSystem2();
printf("c : %d\n", c);
getchar();
return 0;
}
(3) 设置默认对齐数时不考虑机器的大小端,只是表示数据在内存中存放的顺序不同,对数据结构本身没有影响。
一般在不同类型的机器之间通过网络传送二进制数据时,需要考虑大小端问题
5、This指针
class Date
{
public :
void Display ()
{cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
void SetDate(int year , int month , int day)
{
_year = year;
_month = month;
_day = day;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
int main()
{
Date d1, d2;
d1.SetDate(2018,5,1);
d2.SetDate(2018,7,1);
d1.Display();
d2.Display();
return 0;
}
1、Date类中有SetDate与Display两个成员函数,函数体中没有关于不同对象的区分,那当s1调用SetDate函数时,该函数是如何知道应该设置s1对象,而不是设置s2对象呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个“成员函数“增加了一个隐藏的指针参数,让该指
针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
2、this指针的特性
- this指针的类型:类类型* const
- 只能在“成员函数”的内部使用
- this指针本质上其实是一个成员函数的形参,当对象调用成员函数时,将对象地址作为实参传递给this 形参。所以对象中不存储this指针。
- this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递
3、问题:
1、this指针可以为空吗?
答:this指针不可以为空,访问成员变量会去对象里面找,需要解引用用到对象,为空则会出错。
2、this指针存在哪里?
答:this指针存在栈里,因为其是作为成员函数的形参,所以在栈里,在vs下存在于ecx寄存器里面
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
void Show()
{
cout<<"Show()"<<endl;
}
private:
int _a;
};
int main()
{
Date* p = NULL;
p->PrintA();//程序崩溃
p->Show();//正常运行
}
Date *p为一个空指针,调用Show()的时候,会正常运行,因为show()是一个成员函数,存储在公共代码段,不用对指针解引用;
而PrintA()函数,内部需要打印成员变量_a,需要对指针进行解引用,去其对象里面找,在解引用过程中会出错。
可得:This指针可以为空,不能调用修改成员变量的成员函数,成员函数是在公共区域的。
知识点:(附)
1、
Vs和Linux下的各个变量所占字节数:
VS:
(1)32位:
char=1
pointer=4
short=2
int=4
long=4
long long=8
float=4
double=8
(2)64位:
pointer=8
Linux:
(1)32位:
2位:
char=1
pointer=4
short=2
int=4
long=4
long long=8
float=4
double=8
(2)64位:
pointer=8
long=8