首页 > 编程语言 >【c++】结构体和类详解

【c++】结构体和类详解

时间:2024-11-12 19:15:46浏览次数:3  
标签:定义 体和类 int 成员 Object value 详解 c++ 构造函数

结构体

结构体(struct),可以看做是一系列称为成员元素的组合体。

可以看做是自定义的数据类型。

Note

本页描述的 struct 不同于 C 中 struct,在 C++ 中 struct 被扩展为类似 class 的类说明符。

定义结构体

struct Object {
  int weight;
  int value;
} e[array_length];

const Object a;
Object b, B[array_length], tmp;
Object *c;

 

上例中定义了一个名为 Object 的结构体,两个成员元素 value, weight,类型都为 int

在 } 后,定义了数据类型为 Object 的常量 a,变量 b,变量 tmp,数组 B,指针 c。对于某种已经存在的类型,都可以使用这里的方法进行定义常量、变量、指针、数组等。

关于指针:不必强求掌握。

定义指针

如果是定义内置类型的指针,则与平常定义指针一样。

如果是定义结构体指针,在定义中使用 StructName* 进行定义。

struct Edge {
  /*
  ...
  */
  Edge* nxt;
};

上例仅作举例,不必纠结实际意义。

访问/修改成员元素

可以使用 变量名.成员元素名 进行访问。例如可以使用 cout << var.v 来输出 var 的 v 成员。

也可以使用 指针名->成员元素名 或者 使用 (*指针名).成员元素名 进行访问。例如使用 (*ptr).v = tmp 或者 ptr->v = tmp 可以将结构体指针 ptr 指向的结构体的成员元素 v 赋值为 tmp:。

为什么需要结构体?

首先,条条大路通罗马,可以不使用结构体达到相同的效果。但是结构体能够显式地将成员元素(在算法竞赛中通常是变量)捆绑在一起,如本例中的 Object 结构体,便将 value,weight 放在了一起(定义这个结构体的实际意义是表示一件物品的重量与价值)。这样的好处边是限制了成员元素的使用。
想象一下,如果不使用结构体而且有两个数组 value[], Value[],很容易写混淆。但如果使用结构体,能够减轻出现使用变量错误的几率。

并且不同的结构体(结构体类型,如 Object 这个结构体)或者不同的结构体变量(结构体的实例,如上方的 e 数组)可以拥有相同名字的成员元素(如 tmp.value,b.value),同名的成员元素相互独立(拥有独自的内存,比如说修改 tmp.value 不会影响 b.value 的值)。
这样的好处是可以使用尽可能相同或者相近的变量去描述一个物品。比如说 Object 里有 value 这个成员变量;我们还可以定义一个 Car 结构体,同时也拥有 value 这个成员;如果不使用结构体,或许我们就需要定义 valueOfObject[],valueOfCar[] 等不同名称的数组来区分。

如果想要更详细的描述一种事物,还可以定义成员函数。请参考  获取详细内容。

更多的操作

详见 类。

注意事项

为了访问内存的效率更高,编译器在处理结构中成员的实际存储情况时,可能会将成员对齐在一定的字节位置,也就意味着结构中有空余的地方。因此,该结构所占用的空间可能大于其中所有成员所占空间的总和。

 

类(class)是结构体的拓展,不仅能够拥有成员元素,还拥有成员函数。

在面向对象编程(OOP)中,对象就是类的实例,也就是变量。

C++ 中 struct 关键字定义的也是类,上文中的 结构体 的定义来自 C。因为某些历史原因,C++ 保留并拓展了 struct

定义类

类使用关键字 class 或者 struct 定义,下文以 class 举例。

class ClassName {
  ...
};

// Example:
class Object {
 public:
  int weight;
  int value;
} e[array_length];

const Object a;
Object b, B[array_length];
Object *c;

与使用 struct 大同小异。该例定义了一个名为 Object 的类。该类拥有两个成员元素,分别为 weight,value;并在 } 后使用该类型定义了一个数组 e

定义类的指针形同 struct

访问说明符

不同于 struct 中的举例,本例中出现了 public,这属于访问说明符。

  • public:该访问说明符之后的各个成员都可以被公开访问,简单来说就是无论 类内 还是 类外 都可以访问。
  • protected:该访问说明符之后的各个成员可以被 类内、派生类或者友元的成员访问,但类外 不能访问
  • private:该访问说明符之后的各个成员 只能 被 类内 成员或者友元的成员访问,不能 被从类外或者派生类中访问。

对于 struct,它的所有成员都是默认 public。对于 class,它的所有成员都是默认 private

关于 "友元" 和 "派生类",可以参考下方折叠框,或者查询网络资料进行详细了解。

对于算法竞赛来说,友元和派生类并不是必须要掌握的知识点。

关于友元以及派生类的基本概念

友元(friend):使用 friend 关键字修饰某个函数或者类。可以使得在 被修饰者 在不成为成员函数或者成员类的情况下,访问该类的私有(private)或者受保护(protected)成员。简单来说就是只要带有这个类的 friend 标记,就可以访问私有或受保护的成员元素。

派生类(derived class):C++ 允许使用一个类作为 基类,并通过基类 派生 出 派生类。其中派生类(根据特定规则)继承基类中的成员变量和成员函数。可以提高代码的复用率。

派生类似 "is" 的关系。如猫(派生类)"is" 哺乳动物(基类)。

对于上面 private 和 protected 的区别,可以看做派生类可以访问基类的 protected 的元素(public 同),但不能访问 private 元素。

访问与修改成员元素的值

方法形同 struct

  • 对于变量,使用 . 符号。
  • 对于指针,使用 -> 符号。

成员函数

成员函数,顾名思义。就是类中所包含的函数。

常见成员函数举例

vector.push_back();
set.insert();
queue.empty();
class Class_Name {
  ... type Function_Name(...) { ... }
};

// Example:
class Object {
 public:
  int weight;
  int value;

  void print() {
    cout << weight << endl;
    return;
  }

  void change_w(int);
};

void Object::change_w(int _weight) { weight = _weight; }

Object var;

该类有一个打印 Object 成员元素的函数,以及更改成员元素 weight 的函数。

和函数类似,对于成员函数,也可以先声明,在定义,如第十四行(声明处)以及十七行后(定义处)。

如果想要调用 var 的 print 成员函数,可以使用 var.print() 进行调用。

重载运算符

何为重载

C++ 允许编写者为名称相同的函数或者运算符指定不同的定义。这称为 重载(overload)。

如果同名函数的参数种类、数量中的一者或多者两两不相同,则这些同名函数被看做是不同的。

需要注意的是:如果两个同名函数的区别仅仅是返回值的类型不同则无法进行重载,此时编译器会拒绝编译!

如果在调用时不会出现混淆(指调用某些同名函数时,无法根据所填参数种类和数量唯一地判断出被调用函数。常发生在具有默认参数的函数中),则编译器会根据调用时所填参数判断应调用函数。

而上述过程被称作重载解析。

重载运算符,可以部分程度上代替函数,简化代码。

下面给出重载运算符的例子。

class Vector {
 public:
  int x, y;

  Vector() : x(0), y(0) {}

  Vector(int _x, int _y) : x(_x), y(_y) {}

  int operator*(const Vector& other) const { return x * other.x + y * other.y; }

  Vector operator+(const Vector&) const;
  Vector operator-(const Vector&) const;
};

Vector Vector::operator+(const Vector& other) const {
  return Vector(x + other.x, y + other.y);
}

Vector Vector::operator-(const Vector& other) const {
  return Vector(x - other.x, y - other.y);
}

// 关于4,5行表示为x,y赋值,具体实现参见后文。

该例定义了一个向量类,并重载了 * + - 运算符,并分别代表向量内积,向量加,向量减。

重载运算符的模板大致可分为下面几部分。

/*类定义内重载*/ 返回类型 operator符号(参数){...}

/*类定义内声明,在外部定义*/ 返回类型 类名称::operator符号(参数){...}

对于自定义的类,如果重载了某些运算符(一般来说只需要重载 < 这个比较运算符),便可以使用相应的 STL 容器或算法,如 sort

如要了解更多,可参见「参考资料」第四条。

可以被重载的运算符

+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> () [] new new [] delete delete []

在实例化变量时设定初始值

为完成这种操作,需要定义 默认构造函数(Default constructor)。

class ClassName {
  ... ClassName(...)... { ... }
};

// Example:
class Object {
 public:
  int weight;
  int value;

  Object() {
    weight = 0;
    value = 0;
  }
};

该例定义了 Object 的默认构造函数,该函数能够在我们实例化 Object 类型变量时,将所有的成员元素初始化为 0

若无显式的构造函数,则编译器认为该类有隐式的默认构造函数。换言之,若无定义任何构造函数,则编译器会自动生成一个默认构造函数,并会根据成员元素的类型进行初始化(与定义 内置类型 变量相同)。

在这种情况下,成员元素都是未初始化的,访问未初始化的变量的结果是未定义的(也就是说并不知道会返回何值)。

如果需要自定义初始化的值,可以再定义(或重载)构造函数。

关于定义(或重载)构造函数

一般来说,默认构造函数是不带参数的,这区别于构造函数。构造函数和默认构造函数的定义大同小异,只是参数数量上的不同。

构造函数可以被重载(当然首次被叫做定义)。需要注意的是,如果已经定义了构造函数,那么编译器便不会再生成无参数的默认构造函数。这会可能会使试图以默认方法构造变量的行为编译失败(指不填入初始化参数)。

使用 C++11 或以上时,可以使用 {} 进行变量的初始化。

关于 {}

使用 {} 进行初始化,会用到 std::initializer_list 这一个轻量代理对象进行初始化。

初始化步骤大概如下

  1. 尝试寻找参数中有 std::initializer_list 的默认构造函数,如果有则调用(调用完后不再进行下面的查找,下同)。
  2. 尝试将 {} 中的元素填入其他构造参数,如果能将参数按照顺序填满(默认参数也算在内),则调用该默认构造函数。
  3. 若无 private 成员元素,则尝试在 类外 按照元素定义顺序或者下标顺序依次赋值。

上述过程只是完整过程的简化版本

class Object {
 public:
  int weight;
  int value;

  Object() {
    weight = 0;
    value = 0;
  }

  Object(int _weight = 0, int _value = 0) {
    weight = _weight;
    value = _value;
  }

  // the same as
  // Object(int _weight,int _value):weight(_weight),value(_value) {}
};

// the same as
// Object::Object(int _weight,int _value){
//   weight = _weight;
//   value = _value;
// }
//}

Object A;        // ok
Object B(1, 2);  // ok
Object C{1, 2};  // ok,(C++11)

关于隐式类型转换

有时候会写出如下的代码

 

看上去十分不符合逻辑,一个 int 类型不可能转化为 node 类型。但是编译器不会进行 error 提示。

原因是在进行赋值时,首先会将 1 作为参数调用 node::node(int),然后调用默认的复制函数进行赋值。

但大多数情况下,编写者会希望编译器进行报错。这时便可以在构造函数前追加 explicit 关键字。这会告诉编译器必须显式进行调用。

class Node {
 public:
  int var;

  explicit Node(int _var) : var(_var) {}
};

 

也就是说 node a=1 将会报错,但 node a=node(1) 不会。因为后者显式调用了构造函数。当然大多数人不会写出后者的代码,但此例足以说明 explicit 的作用。

不过在算法竞赛中,为了避免此类情况常用的是 "加强对代码的规范程度",从源头上避免

销毁

这是不可避免的问题。每一个变量都将在作用范围结束走向销毁。

但对于已经指向了动态申请的内存的指针来说,该指针在销毁时不会自动释放所指向的内存,需要手动释放动态内存。

如果结构体的成员元素包含指针,同样会遇到这种问题。需要用到析构函数来手动释放动态内存。

析构 函数(Destructor)将会在该变量被销毁时被调用。重载的方法形同构造函数,但需要在前加 ~

默认定义的析构函数通常对于算法竞赛已经足够使用,通常我们只有在成员元素包含指针时才会重载析构函数。

class Object {
 public:
  int weight;
  int value;
  int* ned;

  Object() {
    weight = 0;
    value = 0;
  }

  ~Object() { delete ned; }
};

为类变量赋值

默认情况下,赋值时会按照对应成员元素赋值的规则进行。也可以使用 类名称() 或 类名称{} 作为临时变量来进行赋值。

前者只是调用了复制构造函数(copy constructor),而后者在调用复制构造函数前会调用默认构造函数。

另外默认情况下,进行的赋值都是对应元素间进行 浅拷贝,如果成员元素中有指针,则在赋值完成后,两个变量的成员指针具有相同的地址。

// A,tmp1,tmp2,tmp3类型为Object
tmp1 = A;
tmp2 = Object(...);
tmp3 = {...};

如需解决指针问题或更多操作,需要重载相应的构造函数。

标签:定义,体和类,int,成员,Object,value,详解,c++,构造函数
From: https://blog.csdn.net/2401_88851881/article/details/143648171

相关文章

  • C++笔记---智能指针
    1.什么是智能指针1.1 RALL设计思想RAII(ResourceAcquisitionIsInitialization,资源获取即初始化)是一种资源管理类的设计思想,广泛应用于C++等支持对象导向编程的语言中。它的核心思想是将资源的管理与对象的生命周期紧密绑定,通过在对象的构造函数中获取资源,并在析构函数中......
  • 详解连接 MySQL8.4 出现 Client does not support authentication protocol requested
    文章目录项目场景问题描述原因分析解决方案方案一(不推荐)方案二(推荐)总结项目场景在开发过程中,我们在Node.js项目中使用MySQL数据库,并且通过typeorm和mysql库进行数据库连接。然而,在项目启动时,遇到了数据库连接失败的问题,导致项目无法正常运行。问题描述......
  • 打卡信奥刷题(222)用C++信奥P1746[普及组/提高] 离开中山路
    离开中山路题目背景《爱与愁的故事第三弹·shopping》最终章。题目描述爱与愁大神买完东西后,打算坐车离开中山路。现在爱与愁大神在x1,......
  • C++语法·三
    内联函数(inline)简介:用inline修饰的函数叫内联函数,编译时C++编译器会在调用的地方站开内联函数,这样调用函数就不需要创建栈帧了,可以提高效率。内联函数与宏函数:C++中的内联函数与C中的宏函数很相似,都是直接在预处理时展开函数,将函数直接替换到调用位置,不额外创建栈帧。但内联......
  • 【系统架构设计师-2024下半年真题】综合知识-参考答案及部分详解(完整回忆版)
    更多内容请见:备考系统架构设计师-专栏介绍和目录文章目录【第1题】【第2题】【第3题】【第4题】【第5题】【第6~10题】【第11~12题】【第13~14题】【第15题】【第16题】【第17题】【第18题】【第19题】【第20题】【第21题】【第22题】【第23题】......
  • 自动驾驶仿真:软件在环(SIL)测试详解(精简版入门)
    自动驾驶仿真:软件在环(SIL)测试详解一、引言自动驾驶技术的快速发展对测试验证提出了更高要求。软件在环(Software-in-the-Loop,简称SIL)仿真测试作为自动驾驶系统验证的重要手段,通过将自动驾驶的控制软件与虚拟仿真平台结合,实现对自动驾驶系统的软件功能、稳定性和安全性的全面测......
  • 【C++】详细介绍模版进阶,细节满满
    目录一、非类型模版参数:1、介绍:2、使用:3、注意:4、应用二、模版特化(一)、概念(二)、函数模版特化1、步骤:2、举例:3、不建议使用函数模版特化(三)、类模版特化1.全特化:2、偏特化2.1、部分特化2.2、参数更进一步的限制2.3、注意:2.4、普通指针变量传递给const指针变量......
  • PLSQL 最新版永久免费注册(详解)
    打开plsql,登录或者不登录账号都可,进入菜单【帮助】->【注册】弹出框中依次录入以下信息:产品编号:ke4tv8t5jtxz493kl8s2nn3t6xgngcmgf3序列号:264452密码:xs374ca点击【注册】,即注册成功!......
  • C++学习路线(求补充)
    研二女本硕211明年找工作看网上各种经验帖总结了个C++自学路线求各位大佬指正时间有点紧不知道学这些够不够学习内容:黑马C++基础语法书籍:C++primerplus1,2结束后开始刷代码随想录一天两道复习复习语法侯捷视频:侯捷-STL泛型编程(必看)侯捷-C++11新特性(必看)侯捷-......
  • 打卡信奥刷题(221)用C++信奥P1740[普及组/提高] Diamond A&B(1)
    DiamondA&B(1)题目背景由于本题较难,将本题拆做两题,分别为diamondA以及diamondB。本题为DiamondA。题目描述教主上电视了!这个消息绝对是一个爆炸性的新闻。一经传开,大街上瞬间就没人了(都回家看电视去了),商店打烊,工厂停业。大家都把电视机的音量开到最大,教主的声音......