郑州网站建设方案,wordpress先页面再首页,额尔古纳网站建设,dede企业网站CSDN的uu们#xff0c;大家好。这里是C入门的第九讲。 座右铭#xff1a;前路坎坷#xff0c;披荆斩棘#xff0c;扶摇直上。 博客主页#xff1a; 姬如祎 收录专栏#xff1a;C专题 目录 1. 面向过程与面向对象
2. 类的定义
3. 类中的访问限定符
3.1 访问限定符的… · CSDN的uu们大家好。这里是C入门的第九讲。 · 座右铭前路坎坷披荆斩棘扶摇直上。 · 博客主页 姬如祎 · 收录专栏C专题 目录 1. 面向过程与面向对象
2. 类的定义
3. 类中的访问限定符
3.1 访问限定符的作用
4. 对象的实例化以及对象的大小
5. this指针
5.1 this指针的引入
5.2 VS中this指针的优化
5.3 考验你对this指针理解的两道题 1. 面向过程与面向对象
我们之前学习的C语言是面向过程的语言而我们正在学习的C是面向对象的语言(OOP语言)。之前提到过C是对C语言的补充和改进。那么为什么C要引入面向对象的概念呢面向对象又比面向过程好在哪里呢 面向过程关注的是解决问题的步骤将每一个功能都抽象出一个个具体的函数然后逐步解决问题。 例如我们在家洗衣服站在面向过程的角度来看是这个样子 通过一个一个的步骤来解决问题。在编程中就是通过一个一个的函数来实现。 面向对象在解决问题的过程中面向对象的思想是将解决该问题中的事物看作一个一个的对象对象之间各司其职达到解决问题的目的。 例如在洗衣的过程中人这个对象只需要将衣服放进洗衣机等洗衣机洗好之后取出来即可。不需要关注洗衣机是怎么洗衣服的。 现在来分析面向对象与面向过程的优缺点还是太早了。我们只要有一个大致印象面向对象比面向过程更加高级面向对象能够更加方便的将问题模块化 更好的解决问题。但是相比于面向对象面向过程的代码执行效率较高。
2. 类的定义
我们知道C是兼容C语言的C语言的结构体里面是不能定义函数的。但是C的结构体里面是可以定义函数的因为C将结构体升级成为了类像这样
struct A
{int _a;void func(){cout func endl;}
};int main()
{struct A a1;return 0;
}
C将结构体升级成为了类那么结构体的名字就是类型的名字因此在C中定义结构体是不需要加上struct的。
struct A
{int _a;void func(){cout func endl;}
};int main()
{struct A a1;A a1; //c不用加structreturn 0;
}
在C里面更喜欢用class代替struct来定义一个类于是我们顺理成章地推导出了class定义类的方法 class 类名 { // 类的主体包括成员变量和成员函数 }; 注意分号不能少。 // 定义一个类
class B
{int _b;void func(){cout func endl;}
};
上面的代码我们定义了一个类 B。 类中的变量(_b)叫做成员变量类中定义的函数(func) 叫做成员函数 或者方法。
C规定在类中的定义的函数会被自动地视为inline函数但是他最后是不是内联函数还是取决于编译器。
注意是在类中定义的函数如果你是在类内声明类外定义不会被视为内联函数。
struct B
{int _b;void func() //函数的声明定义均在类里面{int a 10;}void func(int a);
};void B::func(int a)
{int b 10;
}int main()
{B b;b.func();b.func(1);return 0;
}
上面的代码func() 函数就是在类内定义的函数 而func(int a) 则是在类外定义的函数通过调试观察汇编代码我们可以看到func()已经是一个内联函数了。 关于内联函数的细节21天学会CDay6----内联函数_姬如祎的博客-CSDN博客 这里还有一个要注意的点类中函数类外定义的写法需要加上类名和域作用限定符告诉编译器这是这个类里面的函数的实现。不然可能会与全局域的函数冲突。 class中的所有成员变量都在其所在的那个类域里面这样做是理所应当的。
3. 类中的访问限定符
我们用struct定义了一个类 A用class定义了一个类 B创建一个变量之后。访问其各自的成员变量。发现struct定义的类可以直接访问但是class定义的类不能直接访问成员变量。这是为啥呢
struct A
{int _a;void func(){cout func endl;}
};class B
{int _b;void func(){cout func endl;}
};int main()
{A a;a._a 10;B b;b._b 10;return 0;
} 这是因为C中每一个类中的成员都会受到访问限定符的限制。我们来看看C中的访问限定符有哪些 其中public表示类成员在类内类外都可以访问protectedprivate均是类成员在类内可以访问在类外不可以访问。protected 与 private之间的区别需要我们学到继承的时候再讲。
于是我们就可以得出结论在没有写访问限定符的时候struct定义的类默认访问限定符是publicclass定义的类默认访问限定符是private。
访问权限的作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。没有下一个访问限定符就是到类的结束位置。
3.1 访问限定符的作用
这里需要uu们回忆一下我们在使用C语言实现的数据结构无论哪一个都行。我们就拿栈来说吧我们当时定义的栈是这样的 C语言数据结构初阶(5)----栈_姬如祎的博客-CSDN博客 //栈的数据类型
typedef int STDataType;
//栈的结构体类比顺序表
typedef struct Stack
{STDataType* a; //栈的顺序存储的数组int top; //栈的top元素int capacity; //数组的容量
} ST;这其中有一个top变量我们在在实现栈的时候提到过top可以指向栈顶元素或者栈顶元素的下一个位置这取决于设计者的实现方式。 因为C语言结构体中的数据是公开的于是就会有程序员在访问栈顶的元素时写出这样的代码
int main
{ST s;s.a[top];
}
是的他不调用你实现的访问栈顶元素的函数而是直接通过你的底层直接访问数据。如果恰巧你实现的栈关于top的定义是实现方式2碰巧他还是一个脾气暴躁的程序员当他看到访问top的时候出现了随机值他可能会直接破口大骂这是谁写的 laji 代码。C语言没有常见数据结构的库也有一部分原因是这个吧。 没有访问限定符的限制用户可以直接访问并修改任意数据造成意料之外的结果甚至导致程序崩溃。而有了访问限定符就能很好的解决这些问题。 我们只需要将stack的底层数据用private修饰就能很好的解决这些问题。同时将对应操作的函数用public修饰提供对外接口供用户使用。 class Stack
{
public:void StackInit(){//}void StackPush(int val){//}//等等private:int* _a;int _capacity;int top;
}; 这就是封装的具体表现了。 封装是面向对象的三大特性之一另外两个是继承和多态。继承和多态后面讲解。 封装将数据和操作数据的方法进行有机结合隐藏对象的属性和实现细节仅对外公开接口来和对象进行交互。 封装本质上是一种管理让用户更方便使用类。 4. 对象的实例化以及对象的大小
思考这样一个问题你定义好了一个类他是否在内存中占有空间呢
答案显然是否定的因为类是对象的描述类在定义好时并不分配内存空间只有在类实例化出对象之后这个对象占有空间。我们完全可以把类当作一张图纸而对象就是根据图纸生产出来的商品。
class A
{
public:void func(){cout func endl;}private:int _a;int* _p;
};int main()
{A a;return 0;
} 那么我们应该如何计算对象的大小呢拿上面的代码来说sizeof(a)结果是什么呢
(这里写sizeof(A) 也行根据对象能算出大小根据类肯定也行撒类比图纸与商品) 在32位机器下结果是8。对象大小的计算方式和结构体大小的计算是一样的都遵循内存对齐。
但是你可能会问成员函数存在哪里呢对象里面没有成员函数是怎么调用的呢
在回答这些问题之前我们先来思考。不同的对象调用类中的函数是调用的同一个吗没错调用的就是同一个。 既然所有的对象都会调用相同的成员函数那么为什么还要浪费空间在每个对象里面存一份函数的地址呢因此类的成员函数是存在公共代码段的源文件编译的时候编译器会找到函数的地址我们不必关心。
既然对象里面只存储成员变量那要是我定义的类里面没有成员变量那他还会有空间吗 我们看到结果是1如果没有成员变量就没有空间的话我们应该用什么来表征这个对象的存在呢因此即使没有成员变量的类实例化出的对象也是有空间的至于具体的大小依编译器而定。
5. this指针
5.1 this指针的引入
我们创建了一个Date 类类的成员变量用来存储年月日。InitiDate函数用来初始化一个Date对象的日期ShowDate用来打印Date对象表示的日期。
class Date
{
public:void InitDate(int year, int month, int day){_year year;_month month;_day day;}void ShowDate(){cout _year - _month - _day endl;}private:int _year;int _month;int _day;};int main()
{Date d1;d1.InitDate(2004, 01, 01);d1.ShowDate();Date d2;d2.InitDate(2008, 01, 01);d2.ShowDate();
}
我们上一个小节讲到了一个类实例化出来一个对象就会开辟一份属于自己空间那么上述代码中的d1d2是两个不同的对象也就各自拥有一份空间来存储各自表示的日期。但是类的成员函数只有一份他是怎么做到不同的对象调用同一个函数时找到不同空间中的数据(d1调用ShowDate打印的是d1中存储的信息d2调用ShowDate打印的是d2中存储的信息)的呢
这就得讲讲我们的this指针了C语法规定对于非静态成员函数(没有加static修饰的成员函数)编译器会对成员函数做处理在普通成员函数中加了一个隐藏的指针参数让这个指针指向当前对象( 正在调用这个函数的对象d1调用ShowDatethis指向的就是d1这个对象)。需要注意的是this指针不能在形参和实参显示传递但可以在函数内部显示使用。
例如ShowDate函数可以这样写
void ShowDate()
{cout this-_year - this-_month - this-_day endl;
}
现在我们就能理解为什么同一个函数能访问不同的空间了吧因为隐藏传递了一个this指针打印_year等变量实际上是通过传递过来的this指针找到调用该函数的对象中的数据。因此才能做到一个函数访问两块空间。 那这里我就要问一个问题了各位uu觉得this指针存储在哪里呢 A对象 B栈 C堆 答案B。
A如果this指针存在对象中那么我们刚才计算对象大小的时候并没有计算this指针哇因此排除a。
我们再来看看this的定义嘛this是对象这个实参传递过来的那么this就是形参撒uu们形参当然是存在栈中的撒。因此this就是存在栈中的
5.2 VS中this指针的优化
我们来看看VS对this指针的优化我们通过反汇编来看看this指针的传递 我们可以看到VS中直接将this指针存在了寄存器中我们都知道寄存器的读写速度是非常快的在类的内部我们需要大量访问成员变量。而访问成员变量本质上是通过this指针来访问的。因此将他存储到ecx寄存器中能够提高访问的效率。
5.3 考验你对this指针理解的两道题
// 1.下面程序编译运行结果是 A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void Print(){cout Print() endl;}
private:int _a;
};int main()
{A* p nullptr;p-Print();return 0;
}
// 2.下面程序编译运行结果是 A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void PrintA(){cout _a endl;}
private:int _a;
};int main()
{A* p nullptr;p-PrintA();return 0;
}
我们看到这两道题都是通过一个空对象去调用函数区别是在函数中是否访问对象中的成员变量。
答案1运行正常 2运行崩溃
在调用类成员函数的时候会隐士传递this指针都传递的是一个空指针题目一并没有对this指针解引用但是题目二确尝试解引用当问成员变量。因为操作成员变量的本质都是通过this指针因此题目二会发生空指针的解引用引起程序崩溃。