【问题标题】:Switching from C# to C++. What's wrong with my code? do I NEED headers for what I'm trying to do? Class definitions within one file issue从 C# 切换到 C++。我的代码有什么问题?我需要标题来做我想做的事吗?一个文件问题中的类定义
【发布时间】:2012-02-18 06:42:39
【问题描述】:

我已经用 C# 编程几年了,因为它是我的第一语言。我正在努力复习我的 C++,因为我很快就会着手编写一些用 C++ 编写的代码。

这段代码有什么问题:(我知道可能有很多问题。C++ 与 C# 的需求大不相同)。有人告诉我,我不知道如何在 C++ 中正确声明类,并且我需要使用头文件来定义我的类。我需要标题吗?这是一个小程序,只是为了测试,想知道没有它是否可以完成。缺少的标题是这里唯一的问题吗?我有一个关于无法在 Company 中访问 Parse 的错误,但是当我在 Company 类名前面添加 public 时,它会引发更多错误。

啊!好郁闷。

#include "std_lib_facilities.h"
using namespace std;

class Employee
{
public:
    string screen_name;
    string real_name;
    string current_job;
    int employee_number;
    Employee(int no, string name1, string name2, string current_jobin)
    {
        screen_name=name1;
        real_name=name2;
        employee_number=no;
    current_job=current_jobin;
    }
};

class Project
{
public:
    Vector<Employee> Employees;
    int max_worker_quota;
    int project_id;
    string project_name;
    Project(int no_in,int max_in,string title_in)
    {
        max_worker_quota=max_in;
        project_name=title_in;
        project_id=no_in;
    }
};

unsigned int split(const std::string &txt, vector<std::string> &strs, char ch)
{
    unsigned int pos = txt.find( ch );
    unsigned int initialPos = 0;
    strs.clear();

    // Decompose statement
    while( pos != std::string::npos ) {
        strs.push_back( txt.substr( initialPos, pos - initialPos + 1 ) );
        initialPos = pos + 1;

        pos = txt.find( ch, initialPos );
    }

    // Add the last one
    strs.push_back( txt.substr( initialPos, std::min( pos, txt.size() ) - initialPos + 1));

    return strs.size();
}

class Company
{
Vector<Employee> Employeelist;
Vector<Project> Projectlist;

    void Parse(string input)
    {
        //Case Statements
        vector<string> temp;
        split( input, temp, ' ' );

        if (temp[0]=="S")
        {
            //Add Employee to Company
            Employee myEmployee=Employee(atoi(temp[1].c_str()),temp[2],temp[3],temp[4]);
            Employeelist.push_back(myEmployee);
        }
        else if (temp[0]=="C")
        {
            //Add Project to Company
            Project myProject=Project(atoi(temp[1].c_str()),atoi(temp[2].c_str()),temp[3]);
            Projectlist.push_back(myProject);
        }
        else if (temp[0]=="L")
        {
            //Add Employee to Project list
            //Not Implemented-Find Project by temp[1] which is a int
        }
        else if (temp[0]=="A")
        {
        }
        else if (temp[0]=="D")
        {
        }
        else if (temp[0]=="PS")
        {
        }
        else if (temp[0]=="PC")
        {
        }
    }
};

int main(int argc, char *argv[])
{
string input;
cout<<"Command:: ";
cin>>input;
Company myCompany;
myCompany.Parse(input); //Input is in the format X Name Name etc etc. Arguments separated by spaces

return 0;
}

【问题讨论】:

  • C++ 不关心标题,它关心翻译单元。
  • 有什么错误?你使用的那个标题,是 Bjarne Stroustrup 书中的那个吗? This one?
  • 您遇到了什么错误?
  • 如果将所有代码放在一个文件中,则不需要 标头。但是你确实需要正确缩进你的代码。请。
  • 是的。标题来自他的书。好发现!!我现在遇到的错误是 Parse 无法访问的问题。我也简单地为该方法添加了一个公共,它现在可以工作了。但我主要担心的问题是我的一个朋友说我在宣布我的课程完全错误。我需要单独的标题来管理所有内容。仍然对那部分感到困惑。看起来像是编译的,所以我实际上并不需要它们,而是根据其他人做。将尝试对此进行更多研究。

标签: c++ class header compiler-errors private-members


【解决方案1】:

您使用public 开始类中的公共方法部分,而不是类本身。也就是说,您应该在Company 类中的Parse 方法之前添加public:

class Company
{
  Vector<Employee> Employeelist;
  Vector<Project> Projectlist;

public:
  void Parse(string input)
  {

【讨论】:

    【解决方案2】:

    首先,您不需要用于测试目的的标题。但是没有它们你就不能制作一个真正的程序,因为头文件定义了单独编译的程序部分的接口。这就是 C/C++ 的工作方式,没有办法。

    其次,您必须将public: 添加到您的Company 类并处理以下错误。这就像 C# 的东西:你必须定义一个函数 public void Parse(string) 才能从类外部访问它。 C++方式是

    class Foo { 
    public:
    void Bar();
    }; 
    

    第三,在 C++ 中,在类定义中定义非平凡函数是非常规的(唯一的例外是模板类)。这是标题故事的另一面。

    好的,这里简单解释一下与标头相关的基本内容。

    程序通常分为一组单独编译的文件,即翻译单元。每个单元通常由一个.cpp 文件和一个或多个头文件(.h)组成。编译这些文件后,您将获得一个二进制 .obj 文件。此文件包含 objects - 初始化全局(和命名空间)对象所需的函数和内容的代码。要制作程序,您需要将一个或多个目标文件传递给链接器。在 VS 中,它发生在幕后。您只需将 .cpp 文件添加到项目树中,IDE 就会相应地配置项目依赖项。

    这就是您的代码的样子:

    //File: employee.h
    
    #ifndef EMPLOYEE_HDR  /* include guard */
    #define EMPLOYEE_HDR
    
    #include <string>
    
    using std::string;
    
    class Employee {
    public:
    Employee (int no, string name1, string name2, string current_jobin);
    
    void PayBonus(int amount);
    
    void Fire(string reason);
    
    int GetSalary() const
    { return m_Salary; }
    
    /*...*/
    protected:
    int m_Salary;
    string m_FirstName;
    /* ... */
    };
    
    #endif
    
    //File: employee.cpp
    #include "employee.h"
    
    Employee::Employee (int no, string name1, string name2, string current_jobin)
    {
     //definition
    }
    
    void Employee::PayBonus(int amount)
    {
     //definition
    }
    
    void Employee::Fire(string reason)
    {
    //definition
    }
    
    /* define other non-trivial class functions here */
    
    //File: company.h
    
    #ifndef COMPANY_HDR  /* include guard */
    #define COMPANY_HDR
    
    #include <vector>
    using std::vector;
    
    #include "employee.h"
    
    class Company {
    public:
    Company();
    void Hire(string name);
    
    void WorldCreditCrunch() //life is unfair
    { FireEveryone(); }
    
    void Xmas(); //pays $5 bonus to everyone
    
    /* ... */
    
    protected:
    
    vector<Employee> m_Staff;
    
    void FireEveryone();
    
    /* ... */
    };
    
    #endif
    
    //File: company.cpp
    
    #include "company.h"
    
    Company::Company()
    {
     //definition
    }
    
    void Company::Hire(string name)
    {
         //calculate new employee ID etc
         m_Staff.push_back(Employe( /*...*/));
    }
    
    void Company::FireEveryone()
    {
        for(size_t i = 0; i < m_Staff.size(); ++i)
             m_Staff[i].Fire();
    
    }
    
    void Company::Xmas()
    {
        for(size_t i = 0; i < m_Staff.size(); ++i)
             m_Staff[i].PayBonus(5);
    }
    
    /* ... */
    
    //File: main.cpp
    
    #include "company.h"
    
    int main()
    {
         Company c;
    
         c.Hire("John Smith");
    
         /* ...*/
    
         return 0;
    }
    

    所以,基本上我们会有 employeecompanymain 单位。 employee.h 中 Employee 类的定义包含非平凡的函数声明。像GetSalary() 这样的简单函数在类内部定义。它提示编译器内联它。 employee.cpp 包含其余的函数定义;

    company.h 文件有 #include "employee.h" 预处理器语句。所以我们可以在类定义和它的实现文件(company.cpp)中使用Employee对象。

    main.cpp 包含程序入口点。它可以使用 Company 类,因为它包含“company.h”。

    如果我们更改 Employee::Hire() 函数实现中的某些内容,则只会重新编译 employee.obj。这是此类节目组织的主要目的。但是如果我们更改 Employee 接口(@98​​7654336@ 中的类定义),每个程序单元都需要重新编译。

    如果您执行以下操作,则需要包含警卫:

    #include "employee.h"
    #include "company.h"  /* will contain the 2nd inclusion of employee.h which will be 
                            prevented by include guard */
    

    基于 Microsoft Visual C++ 的项目经常使用#pragma once 来实现相同的目的。它更易于使用,但通常不便于携带。

    例如,如果您将 Employee::Hire 定义放在 employee.h 中,编译器会将函数代码放在employee.obj 和 company.obj 中(因为company.h 包括employee.h)。当您尝试在这种情况下链接时,链接器将遇到相同功能代码的 2 个版本并给出错误。内联函数不会编译到单独的实体中,因此不会导致此类错误。仅在模板实例化时生成的模板代码也是如此。因此,多个翻译单元可能具有相同的非内联模板函数的代码。

    由程序员定义程序的部分边界。如果需要,您可以将 Company 和 Employee 放入单个翻译单元。 VS 向导倾向于为每个主要类创建一个 .h/.cpp 对。试着做一个 MFC 项目,自己看看。

    这些是基础。正如我在上面的评论中提到的,您可以从 Stroustrup 的“C++ 编程语言”中全面了解这些内容

    【讨论】:

    • 您介意扩展您的第三点吗?我想学习如何用 C++ 编写代码,这样我就可以编写其他人可以理解的“标准编写”代码。您介意就您的第一点“单独编译的程序部分”给出一个简短/简短的示例吗?非常感谢
    • @user1147223:我已经添加了解释。祝你学习 C++ 好运:)
    • 这非常有帮助。非常感谢
    【解决方案3】:

    这只是为了扩展 Pavel 的第三点,因为您在评论中提出了要求。

    他的意思基本上是这样的:在任何真正的 C++ 项目中,您都希望将类的定义与其实现分离为两个文件,一个头文件 (*.h) 和实现文件(*.cpp) .使用上面的示例,它可以像这样分开:

    // Employee.h
    #ifndef EMPLOYEE_H
    #define EMPLOYEE_H
    #include <string>
    
    class Employee
    {
    public:
        Employee(int no, 
                 std::string name1, 
                 std::string name2, 
                 std::string current_jobin);
    
    private:
        std::string screen_name;
        std::string real_name;
        std::string current_job;
        int employee_number;    
    };
    #endif
    

    // Employee.cpp
    #include "Employee.h"
    
    Employee::Employee(int no, 
                       std::string name1, 
                       std::string name2, 
                       std::string current_jobin)
    {
        screen_name = name1;
        real_name = name2;
        employee_number = no;
        current_job = current_jobin;
    }
    

    // Company.h
    #ifndef COMPANY_H
    #define COMPANY_H
    #include <vector>
    #include <string>
    #include "Employee.h"
    #include "Project.h"
    
    class Company
    {
    public:
        void Parse(std::string input);
    
    private:
        std::vector<Employee> Employeelist;
        std::vector<Project>  Projectlist;
    };
    #endif
    

    // Company.cpp
    #include "Company.h"
    
    void Company::Parse(std::string input)
    {
        // your code for Parse in Company goes here
    }
    

    你的 main.cpp 会像这样使用上面的:

    // main.cpp
    #include <iostream>
    #include <string>
    #include "Company.h"
    
    int main(int argc, char *argv[])
    {
        std::string input;
        std::cout << "Command:: ";
        std::cin >> input;
        Company myCompany;
        //Input is in the format X Name Name etc etc. Arguments separated by spaces    
        myCompany.Parse(input); 
    }
    

    现在您来自 C# 背景,您可能想知道为什么会以这种方式完成 - 毕竟您最终会得到两倍的文件!简短的回答是因为 C 是这样做的,而 C++ 从 C 那里继承了很多包袱。

    更长的答案是因为 C++ 不像 C# 和 Java 那样使用“模块”系统。在构建源文件时,编译器除了正在编译的当前文件外,不会看其他任何地方。因此,编译器知道您正在使用的类、函数或变量是否存在的唯一方法是,它是否出现在使用前正在编译的源文件中的某个位置。包含由#include 指令引入的所有头文件以及源 .cpp 的主体是所谓的“编译单元”——它是一个独立的单元,包含编译器成功编译所需知道的所有内容不看别处。

    【讨论】:

    • 感谢 C# 参考。因为我想知道你到底回答了什么! :)
    猜你喜欢
    • 2020-02-03
    • 1970-01-01
    • 1970-01-01
    • 2017-05-02
    • 1970-01-01
    • 2015-06-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多