赞
踩
C语言是一们面向过程的语言,关注的是函数执行的过程,数据和方法是分离的。
C++是一门面向对象的语言,主要关注对象,将一件事情抽象成不同的对象,靠对象间的交互来完成。对象与对象之间的交互叫做消息(举例外卖系统中,分为快递小哥,商家,菜品,用户这几个类,通过这些类创建出各自的对象,通过他们的交互来完成一次外卖行为)
关于类的定义这里因为C++是兼容C语言的,所以C++给C语言的struct一个新的定义,就是用来定义类,C语言中关于结构体的用法C++这里同样也是兼容的。
struct name
{
void Init()
{
;
}
void Push()
{
;
}
int* _a;
int _top;
int _capacity;
};
定义一个类实际就是一个类型(花括号括起来的就是一个作用域,类的作用域就叫类域),类里面可以定义成员变量_top等,也可以定义成员函数就是Init和Push。name就是类名,成员变量和成员函数组成了类体。
struct毕竟是C语言的产物,C++还有一个关键字class来定义类
class stu { public: void Init(const char*arr,const char*id,const char*sex) { strcpy(_name, arr); strcpy(_id, id); strcpy(_sex, sex); } char _name[20]; char _id[15]; char _sex[5]; }; int main() { struct stu s; s.Init("zhangsan", "0007", "nan"); return 0; }
类里面定义实现的函数会被编译器默认当成内联函数。
声明与定义分离如下所示
//main.cpp #define _CRT_SECURE_NO_WARNINGS #include"stu.h" int main() { stu s; s.Init("zhangsan", "0007", "nan"); return 0; } //stu.h #pragma once #include<iostream> #include<cstring> using namespace std; class stu { public: void Init(const char* arr, const char* id, const char* sex); char _name[30]; char _id[20]; char _sex[10]; }; //stu.c #define _CRT_SECURE_NO_WARNINGS #include"main.h" void stu::Init(const char* arr, const char* id, const char* sex) { strcpy(_name, arr); strcpy(_id, id); strcpy(_sex, sex); }
上面代码中的public就是类的访问限定符。
类的访问限定符一共有三个:public,private,protected在此处暂且认为private和protected是相同的。其中public的意思是公有,在public的范围之下(从public出现的地方到下一个访问限定符出现或者到类结束},这段范围都是public)的成员函数或者成员变量是可以在类外被直接访问到的。比如上面代码中的成员函数Init或者成员变量id_[20],可以直接在外面被修改。但是在private或者protected的范围内的成员在类外是不可见的。
//main.cpp #define _CRT_SECURE_NO_WARNINGS #include"stu.h" int main() { class stu s; s.Init("zhangsan", "0007", "nan"); return 0; } //stu.h #pragma once #include<iostream> #include<cstring> using namespace std; class stu { public: void Init(const char* arr, const char* id, const char* sex); private: char _name[30]; char _id[20]; char _sex[10]; }; //stu.c #define _CRT_SECURE_NO_WARNINGS #include"main.h" void stu::Init(const char* arr, const char* id, const char* sex) { strcpy(_name, arr); strcpy(_id, id); strcpy(_sex, sex); }
在stu类里面加了一个private,这样就限定类外不能直接访问到成员对象,只能通过成员函数来访问成员对象,好处就是,用户的行为都在掌控之中,不会出现,直接修改成员对象导致某些错误的情况发生。
如果不加类访问限定符,那么struct默认为public(因为struct要兼容C语言的结构体所以是能够随意访问的),class默认为private。
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别(后面的多态,会存在方法突破类域限制)
**【面试题】 **
**问题:C++中struct和class的区别是什么? **
解答:C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。 和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是的成员默认访问方式是private。
封装实际上是将数据与操作数据的方法结合起来,然后隐藏对象的部分属性和实现细节(一般成员变量都是隐藏的)对外只公开接口,通过接口来对数据进行操作。
封装可以使得数据更加安全,不存在说用户直接去修改对象属性的这种危险行为,用户只能通过调用函数接口来访问对象,行为更加可控。
所以想给外部展示的成员定义为public,不想给外部展示的定义为private或protected。
定义一个类,这个类的大括号就是这个类的作用域,又叫做类域。想要类外实现类成员,就要指定函数属于那个域。
class stack
{
public:
void Init();
int size;
int capacity;
int* a;
};
void stack::Init()
{
//.....
}
在类外定义类成员就要加stack::。
//stack.cpp
void stack::Init()
{
.....
}
用定义好的类创建出来对象,这个就叫做类的实例化。
class stack { public: void Init() { ; } void Push() { ; } private: int*_a; int _size; int _capacity; }; int main() { stack s1; s1.Init(); s1.Push(); return 0; }
用stack创建出来了一个对象s1,这个就是类的实例化。
仅仅定义一个类是不占用内存的,是创建了一个类型,这相当于是一个蓝图,通过这个蓝图可以创建对象,当对象创建出来之后才会在内存中占用空间。
stack::_a为什么不能访问到a?因为stack类型里实际并没有变量a,要等实例化对象之后才存在_a变量。
static类型的成员变量是一个例外。
这里要注意当计算s1的大小的时候,计算出来的大小是12,那么问题来了,成员函数不占内存吗?
因为实例化出来的对象不包含成员函数,定义类的成员函数都存放在公共的代码段并不是存放在对象中,不然创建多个对象会有很多个重复成员函数,实际应该是多个对象都调用的是同一个成员函数。
class stack { public: void Init() { ; } void Push() { ; } private: }; int main() { stack s1; cout << sizeof(s1) << endl; return 0; }
如果我们不定义成员变量那么计算的大小会是多少呢?会不会是0?
不会,如果这个对象的大小是0,那么定义很多个这样的对象,他们的地址也就无法区分开来了。所以不管是空类(什么成员都没有)或者是没有成员变量的类,他们的大小最小是1,这个一个字节的意义就是为了占位,占了一个地址,可以区分不同的对象 。
类的对象的大小计算是和结构体一样的,同样也是遵守内存对齐的。
总结,再来看下面这段代码中的类的大小分别是多少呢?
// 类中既有成员变量,又有成员函数 class A1 { public: void f1(){} private: int _a; }; // 类中仅有成员函数 class A2 { public: void f2() {} }; // 类中什么都没有---空类 class A3 {};
答案:A1创建出来的对象大小是4,A2和A3都是1,所以现在我们也可看出来,对象中不包含成员函数。
结构体内存对齐规则
注意:只有vs有默认对齐数,其他编译器都是以自身类型大小作为自己的对齐数。
#pragma pack (8)
struct test2
{
char c;
double a;
};
int main()
{
stack s1;
cout << sizeof(struct test2) << endl;
return 0;
}
这段程序可以很好的说明最大对齐数,当默认对齐数为1的时候,这大小是9
因为默认对齐数是1,和变量类型中最大的8取较小值就是1.
当默认对齐数为4的时候这个结构体大小为12,8和4取较小的取到了4.
当默认对齐数是8,就没区别了,默认和最大都是8,那结果是16.
**【面试题】 **
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
3,如何知道结构体成员的偏移量呢?
使用宏offsetof(注意这是个宏不是函数),引用的头文件是
#pragma pack (8)
#include<cstddef>
struct test2
{
char c;
double a;
};
int main()
{
stack s1;
//cout << sizeof(struct test2) << endl;
cout << offsetof(struct test2, a)<<endl;
return 0;
}
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; d1.Init(2022, 6, 17); d1.Print(); return 0; }
下面我们来思考问题,当我们用不同的对象都调用Print函数和Init函数的时候,这个函数是如何区分形参要赋值给哪一个对象的成员变量呢?
这就是我们要说的this指针
//其实我们在调用Init的时候看似是这样传参
d1.Init(2022,6,17);
//实际上是
d1.Init(&d1,2022,6,17);
d1.Print();
d1.Print(&d1);
在传参的时候实际上还将对象的地址传了过去,只是这些是编译器自己处理的我们看不到而已。下面再来看函数部分是如何实现的。
//实际上的Init是这样的
void Init(Date*const this,int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
//Print也是如此
void Print(Date*this)
{
cout << this->_year << " " << this->_month << " " << this->_day << endl;
}
就像是C语言中的结构体指针,通过箭头来访问结构体内的对象,这个this指针就是同这种方式访问到每个对象的成员变量。
但是参数上的Data * const this是编译器隐含加上的,不可以手动加上,否则在传参的时候就会出现对应不上的问题。但是我们是可以直接在这个成员函数内使用this指针的,比如下面这样:
void Init(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
总结:
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
1,this指针的类型是:Date* const this,因为this指针是不可修改的所以用const保护起来。
2,this指针只能在非静态成员函数中使用。
3,this指针是成员函数的一个形参,是对象在调用函数的时候将对象的地址当作实参传递的。所以this指针并不在对象中,而是在存在栈区里面(this指针就是一个指针,一个局部变量)。
4,this指针是成员函数第一个隐含的指针形参,vs编译器通过ecx寄存器自动传递,不需要用户传递。
// 1.下面程序能编译通过吗? // 2.下面程序会崩溃吗?在哪里崩溃 class A { public: void PrintA() { cout << _a << endl; } void Show() { cout << "Show()" << endl; } private: int _a; }; int main() { A* p = NULL; p->PrintA(); p->Show(); }
这段代码可以编译通过,但是执行的时候会挂掉,p->PrintA的时候并没有对空指针进行解引用,虽然这个p指针是空指针,但是调用PrintA函数的时候只是把p当作参数传给了this指针,因为PrintA并不在对象中,所以也不需要对p解引用到对象里面找这个函数,而是需要到公共代码段内找这个函数,因此是可以编译通过的。
程序挂掉的原因是在PrintA中访问_a,实际上是this->_a。这里对空指针进行了解引用,所以程序崩溃了,如果屏蔽掉PrintA则程序没有问题。
注意:PrintA和Show函数的地址并没有存在对象里面。这些成员函数都是存在公共代码段的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。