作者主页
lovewold少个r博客主页
⚠️本文重点:C++入门知识点以及类和对象的初步了解
每日一言:实践能力是自学能力最终转化为真正价值的根本。
前言
auto关键字
auto关键字新用法
auto使用细节
auto与指针和引用结合起来使用
一行声明多个变量
auto不能推导情况
基于for范围的循环(C++11)
范围for的使用条件
指针空值
类和对象(重点)
什么是类和对象
类的定义
类的两种定义方式
类的限定访问符及封装
class和struct的区别
封装
类的作用域
类的实例化
类对象模型
如何计算类对象的大小
this指针
总结
本章主要讲述C++的类和对象特性,同时对auto关键字进行一个简要讲解,并简要完善前面章节未完成部分。在本章节,我们主要学习到auto关键字的合理用途,接触类和对象的面纱。能理解什么是面向过程,什么是面向对象,并且对类进行一个初步了解,了解在规则上的特点和用处。
auto关键字是C++11加入的一个新特性,让编译器能够根据初始值的类型判断变量的类型。其主要的目的还是解决随着程序越来越复杂引起的各种类型复杂命名,特别是对于自定义类型的难以拼写和含义不明确容易出错。
看到上述代码,单从类型名的意义上来讲,可读性还是非常高的,但是这如此长的一串不是折磨人么。std::map::iterator 是一个类型,但是该类型太长了,特别容易写错。在C语言中,我们就会用typedef来重命名解决这个问题。
当然,这样也会引发新的问题:
当我们要把表达式的值赋值给变量的时候就得要求声明时候清楚的知道表达式的类型,但是很显然这样子是不容易通过typedef就能实现的,因此C++11提供了auto关键字,赋予了新的含义。
auto和早期的C和C++中的auto修饰变量不同,C++11赋予了auto的全新含义:作为一个类型提示符来指示编译器,auto声明的变量由编译器在编译时期根据初始化值推导类型。
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;//typeid用来确定变量类型
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}
注意:使用auto声明变量的时候,必须进行初始化,在编译期间编译器就会对初始化的表达值进行推导,来确定变量是什么类型。因此auto不是一种简单的类型声明,可以理解为类型声明占位符,提示编译器推导后将实际类型于auto位置替换。
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。理解起来也很好理解,对于编译器类型推导过程中来讲,能推导右边是取地址还是一个变量,因此对于推导替换过程来讲,x推导为int类型和auto替换成int&;而auto*和auto也能根据右值为什么类型地址而清楚推导类型进行替换。
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
以上代码是错误演示,实际上auto推导只推导一次,并根据推导结果去声明同行其它变量,因此要确保同行类型一致。
- auto不能作为函数的参数
- auto不能用来声明数组
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (auto& e : array)
{
e *= 2;
cout << e << " ";
}
}
上述代码实现了C语言部分我们经常使用的一个for循环遍历一个数组的同等操作,因为对于一个有范围的集合还说,确定范围是多余的,且在边界的范围确定中容易发生越界访问,发生错误。C++11引入了基于范围的for循环。for循环后的的括号由冒号:分为两个部分,一个是用于范围内用于迭代的变量,第二分表示被迭代的范围。
注意:与普通循环一样可以使用continue和break关键字对循环进行控制。
for循环的迭代范围是确定的。
对于数组而言,就是数组的第一个元素和最后一个元素的范围;对于类而言,需要和for循环一致,提供begin和end的方法,用来确定迭代范围。
在C语言中,在养成过良好编程习惯后大概会对为指针进行初始化。int* p=NULL--NULL实际上是一个宏定义,在头文件“stddef.h”中,宏主要是替换作用,所以实际上NULL可能被定义为字面常量0或者被定义为无类型指针(void*)的常量。
这种方式首先是有歧义的,在使用过程中会和程序的设计初衷相悖。程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。除非在使用过程中对其强制类型转换,但是这种细节是很难去避免的。
C++中以nullptr表示指针空值且不需要包含头文件,因为是作为关键字引入。
C++11中。sizeof(nullptr)和sizeof((void*)0)所占字节数相同。
因此在后续C++程序中,更加常用nullptr来表示指针空值。
C++和C的本质区别主要不是语法的转变,而是思维逻辑的转变。在C语言中更强调的是面向程序过程而言。
身体作为人的一部分,是由无数原子构成,C语言对于处理我们动动手指,可能有点像调用身体器官各个部分分别运转,从而达到动动手指这个操作。但是如果我们吧人看作一个整体,身体作为整体的一部分,我们只需要知道动动手指这个操作方式,我们就可以执行。这就是一种面向过程和面向对象的区别。
C++通过类和对象的实现,极大的简化了对于程序编程而言的繁琐,极大的解放了思维提高了效率,这就是语言之间的本质区别。
C语言结构体中只能定义变量,在C++中,不仅可以定义变量,还可以定义函数。类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类被称为类的成员。
看周围真实的世界,会发现身边有很多对象,车,狗,人等等。所有这些对象都有自己的状态和行为。拿一条狗来举例,它的状态有:名字、品种、颜色,行为有:叫、摇尾巴和跑。我们把这个对象的属性和能执行的操作集合为一个类组成一个对象。程序对象也有状态和行为。对象的状态就是属性,什么变量,行为通过方法体现。
Class为定义类的关键字,classname为类名,{}中为类的主体,注意类的定义结束的时候不能省略。类体中的内容称之为类的成员:类的变量称之为类的属性或者成员变量;类中的函数称之为类的方法和成员函数。
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
声明和定义都放在类体中。
class person { public: void showInfo() { cout << _name << "-" << _sex << "-" << _age << endl; } public: char* _name; char* _sex; int _age; };
类声明放在.h头文件中,成员函数定义放在.cpp文件中,注意函数成员名前要加上类名::
首先我们要来理解类的作用域范围。对于一个全局变量而言,可以在全局变量所属文件的任何地方进行使用,而局部变量只能在所属的代码块中使用。
对于类而言,其内部定义的成员名和成员函数作用域都为整个类,作用域为整个类的名称,在内部是已知的,在类外部是不可知的。我们也可以通过访问限定符来决定其成员的作用域。
在类成员的封装中,我们通关访问修饰符能定义其成员相对类外部为公有、私有还是保护。
C++封装方式:用类将对象的属性和方法结合在一起,让对象更加完善,通过访问选择性的将接口提供给外部的用户使用。
- public修饰的成员在类外部可以直接被访问。
- protected和private修饰的成员在类的外部不可以直接访问。
- 访问权限的作用域从该限定符起到下一次限定符出现位置。
- 没有后续的访问限定符,作用域到 } 即类结束。
- class的默认访问权限为private,当数据映射到内存以后,没有任何访问限定符的区别。
C中的struct需要兼容C,因此C++中的struct可以当成结构体使用,另外还可以用来定义类。和class定义类啥一样的,不同的是struct定义的类默认访问权限为pblic,class定义的默认访问权限是private。
在类和对象阶段,主要是研究类的封装特性,那么什么是封装?封装其实就是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅使用公开接口完成对对象的交互。
封装其实是一种底层管理,保证用户更加方便的使用类。我们使用计算机的过程本质就是这样子,我们插入鼠标就可以使用,插上耳机就能听歌,插上键盘就能实现输入。当实际上工作的是计算机的底层,但是我们就通过这样子的封装让使用更加简单。
我们不用用c语言思维去关心内存零部件,比较主板线路布局是这样子的,比较cpu如何设计,都不需要。对于一台计算机他只需要厂商提供接口,外部套上壳子,用户就能完成和计算机的交互工作。
在C++中,通过类的方式将数据和操作数据的方法进行有机结合,通过访问权限来隐藏对象的细节,控制哪些方法能在外部被直接使用。
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外部定义成员时候,需要使用::作用域操作符表示成员属于哪一个类。
class person { public: void PrintPersonInfo(); private: char _name[20]; char _gender[3]; int _age[]; }; //定义函数方法先指明此函数是属于哪一个类的 void person::PrintPersonInfo() { cout << _name << " " << _gender << " " << _age << endl; }
用类型创建类的过程叫做类的实例化
类是对对象进行表述的,是一个模型的东西,限定了类有哪些成员,定义出一个类并没有实际分配内存空间来进行存储。好比一张设计图纸,图纸上面限定了设计的方方面面,各方面细节。但是图纸始终是图纸,没有建造出实例化的对象就不占空间。
一个类可以实例化多个对象,实例化的对象占用实际的物理空间,存储类成员变量。即需要先利用这张设计图设计出来的房子才具备住人等基本功能,人不能住进设计图内。
class A { public: void PrintA() { cout << _a << endl; } private: char _a; };
对于上述的一个类,既有成员变量,又有成员函数,那么一个类的对象包含了什么,如何计算一个类的大小呢?
首先要分析类的存储方式
通过以下一段代码获取大小对存储方式进行分析。
class A1//既有成员变量又有成员函数 { public: void f1() {}; private: int _a; }; class A2//仅有成员函数 { public: void f2() {}; }; class A3//类中什么都没有 {};
一个类的大小,实际就是该类中“成员变量”之和,当然要注意内存对齐的情况。
成员函数实际在一个公共代码区域,因此计算类的大小和空类一致,编译器给空类一个字节来标识这个类的对象。
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 a;
};
int main()
{
Date d1, d2;
d1.Init(2022, 1, 11);
d2.Init(2023, 12, 10);
d1.print();
d2.print();
}
对于上述的一个Date类,有两个成员函数,函数体没有关于对象的区分,那为何d1和d2调用init函数的时候,设置对象却进行了区分呢。
C++通过引用this指针解决该问题,即:C++编译器给每一个非静态成员函数增加了一个隐藏的指针,让该指针去指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作都是通过该隐式指针去访问的。不过所有的操作对用户是透明的,即用户不需要去传递,编译器自动完成。因此,哪有什么岁月静好,只是编译器在为你负重前行。
this指针的特性
每个对象都有一个 "this" 指针:在C++中,每个类的对象都有一个 "this" 指针,它是一个指向对象自身的指针。
"this" 指针的隐含性:无需显式声明或初始化 "this" 指针。它是隐含的,C++编译器自动创建并使用它。
成员函数内使用 "this" 指针:在类的非静态成员函数内,可以使用 "this" 指针来访问对象的成员变量和调用其他成员函数。例如,如果有一个成员变量
x
,可以使用this->x
来引用它。避免命名冲突:使用 "this" 指针有助于避免命名冲突。如果一个成员函数的参数或局部变量与成员变量同名,你可以使用 "this" 指针来明确指定你要访问的是成员变量,而不是局部变量。
const 成员函数中的 "this" 指针:当你有一个 const 成员函数时,"this" 指针的类型变为
const ClassName*
,这意味着在该函数内不能修改对象的成员变量。
本章节主要涵盖了C++中的类和对象特性,以及介绍了C++11中引入的 "auto" 关键字。下面是一些重要的概念和内容的总结:
"auto" 关键字:
"auto" 关键字是C++11引入的一个特性,允许编译器根据初始化值的类型推导变量的类型,从而减少类型复杂性和提高代码可读性。
"auto" 关键字可以用于自定义类型、基本类型和表达式的类型推导。
"auto" 也可以结合指针和引用使用,根据右值类型进行类型推导。
类和对象:
类是C++中的一个重要概念,它允许将数据和操作数据的方法有机地结合在一起,形成一个独立的数据类型。
类定义了对象的形式,包含了数据成员(属性)和成员函数(方法)。对象是类的实例。
成员变量是类的属性,成员函数是类的方法,它们用于访问和操作对象的状态。
封装是类的一个关键特性,它隐藏了对象的内部细节,只允许通过公有接口来访问和操作对象。
访问权限关键字(public、protected、private)用于控制成员的可访问性,限定了哪些成员可以被外部访问。
类和对象的定义,成员函数的定义需要使用作用域操作符
::
来标明属于哪个类。类的实例化和对象模型:
类定义了对象的模型,但类本身并不占用实际内存空间。对象的实例化是用类创建对象的过程,实际分配了内存空间来存储对象的成员变量。
对象的大小取决于其成员变量的大小,不同的类对象可能占用不同大小的内存空间。
"this" 指针:
"this" 指针是一个隐含的指针,指向当前对象的地址。它允许在成员函数中访问对象的成员变量和其他成员函数。
"this" 指针在成员函数内部自动可用,无需显式声明或初始化。
"this" 指针是用来区分不同对象调用同一个成员函数的情况,从而正确地访问各自的成员变量。
本章介绍了C++中的类和对象概念,封装特性,访问权限,以及 "auto" 关键字的使用。这些概念和特性是C++中面向对象编程的核心,用于创建更加模块化和可维护的代码。
作者水平有限,文章难免出现错误,如有错误,欢迎指正!