65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 简要入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.22/5 (4投票s)

2009年7月26日

CPOL

8分钟阅读

viewsIcon

35262

downloadIcon

147

一篇介绍 C++ 一些基本概念的文章。

前言

本文将介绍 C++ 和面向对象编程的一些基础知识,目的是在实际应用中使用 C++。原始的 C++ 语法基于 C 语言。然而,问题出现在 C++ 程序员有时会使用另一个开发者的类库,该类库在同名类中以不同的方式定义成员。这导致了系统崩溃,并引入了命名空间的概念。

ANSI/ISO C 语言是一种面向过程的语言,因此使用函数作为组织的基本单元。C 程序中包含的函数预定义为存在于头文件中。头文件不会被编译,而是会被预处理。源代码被翻译成本地机器语言,生成一个目标文件。可执行文件在链接过程中构建,链接器将包含在这些包含的头文件中的函数定义复制到翻译后的源代码文件上。C++ 是一种面向对象的语言,它使用类作为组织的基本单元。因此,头文件包含类库。类是一种抽象。例如,类 Orange 可能代表一箱橘子。箱子里的橘子就是成员。用于操作这些橘子的方法在语义上是相关的。对象成员的数量是具体定义的。类是一种抽象,用于提供概念视图。在 C++ 中,通过声明一个类来创建一个新类型。类仅仅是数据成员(通常是不同类型)的集合,与一组函数结合在一起。在 C++ 中,头文件声明包含对象成员的类。源代码文件在类中定义的对该类数据成员的方法进行实现。对象绝不会作为一个整体被访问。通过实例化一个类来访问对象涉及三个关键字:publicprivateprotected。以下是一个声明类的示例

class  Dog
{
   unsigned int itsAge;
   unsigned int itsWeigth; 
   void Bark();
}

对于初学者来说,<iostream> 是用于输入输出流到标准输出和从标准输出读取的头文件。“cout” 表示控制台输出,“cin” 表示控制台输入。最好使用 using namespace std; 语句来表明您正在导入标准模板库。声明这个类不会为 Dog 分配内存。它只是告诉编译器 Dog 是什么,它包含哪些数据成员(itsAgeitsWeight),以及它能做什么(Bark())。现在,虽然没有分配内存,但它确实让编译器知道 Dog 的大小(即编译器必须为我们创建的每个 Dog 预留多少空间)。在这个例子中,如果一个整数(Int32)是 4 字节,那么一个 Dog 就是 8 字节:itsAge 是 4 字节,itsWeight 是 4 字节。Bark() 只占用存储 Bark() 位置信息的空间。这是一个指向函数的指针,在 32 位平台上可以占用 4 字节。

公共与私有

上面的代码没有使用任何访问器关键字,因此默认被认为是私有的。私有成员只能在类的方法内部访问。公共成员可以通过类的任何对象进行访问。使用 Dog 来访问数据成员的方法是使某些成员为公共的

class  Dog
{
Public:
   unsigned int itsAge;
   unsigned int itsWeigth; 
   void Bark();
};

现在,我们将编写代码来演示带有公共成员变量的 Dog 的声明

#include <iostream>
class  Dog
{
public:
    int itsAge;
    int itsWeigth; 
    void Bark();
};

int  main() {
{
    Dog Lassie;
    Lassie.itsAge = 5;  // initialize, or assign, to the member variable
    std::cout  <<  "Lassie is a dog who is  "      ;
    std::cout <<  Lassie.itsAge <<  "  years old.\n";
    return 0;
    }
}

编译此代码会产生短语:Lassie 是一只 5 岁的狗。

在设计类型时,应将类的成员数据设为私有。要访问类中的私有数据,必须创建称为访问器方法的公共函数。使用这些方法来设置和获取私有成员变量。这些访问器方法是您程序其他部分调用的成员函数,用于获取和设置私有成员变量:因此,公共访问器方法是用于读取(获取)私有类成员变量的值或设置其值的类成员函数。这就引出了面向对象编程中使用的标准化机制:将接口与其实现分离。添加访问器函数而不是直接读取数据。这会创建间接性,但这种间接性意味着访问器函数使您能够将数据的存储方式与其使用方式分开。通过使用访问器函数,您可以稍后更改数据的存储方式,而无需重写应用程序中其他使用该数据的功能。这就是为什么对象从不被作为一个整体访问,这也是 COM 编程的语言独立性的一项主要原则:接口与实现的解耦隐藏了功能执行的细节。COM 是一个二进制实体,某些语言对同一函数的定义不同。如果接口定义是通用的,那么在二进制级别,所有内存表看起来都一样,函数定义可以存在,只要语言使用返回函数指针的函数。因此,访问器函数为类的私有数据成员提供了公共接口。请检查下面的代码,它说明了一个类的完整声明以及其访问器函数和通用类成员函数的一个实现

#include <iostream>
using namespace std;
class  Dog
{
   public:
    int GetAge();
    void SetAge(int age);
    void Bark();
 
   private:
 
    int itsAge;
};

int Dog::GetAge()
{
   return itsAge;
}

void  Dog::SetAge(int age)
{
  itsAge = age;
}

void Dog::Bark()
{
     std::cout << "Bark.\n";
}

// create  a dog, set its age, have it bark, tell us its age, and then
// have it bark again
int main()
{
  Dog Lassie;
  Lassie.SetAge(5);
  Lassie.Bark();
  std::cout << "Lassie is a dog who is " ;
  std:cout << Lassie.GetAge()  << " years old.\n";
  Lassie.Bark();
  return 0;
}

输出当然是

Bark.
Lassie is a dog who is 5 years old.
Bark.

添加构造函数和析构函数

数据类型或变量总是先声明,然后初始化。例如,“int Weight”声明了一个整型变量,这告诉编译器需要预留 4 字节的存储空间。当我们初始化变量时,我们为其赋值:Weight = 7;。当然,我们可以同时声明和初始化一个变量:int Weight = 7;。您可以使用一个特殊的成员(称为构造函数)来初始化类的成员数据。构造函数可以根据需要接受参数,但它们不能有返回值(甚至不能是 void)。构造函数是一个与类同名的类方法。每当声明构造函数时,您也应该声明一个析构函数。就像构造函数创建和初始化类的对象一样,析构函数在对象不再被对象用户使用后进行清理。也就是说,析构函数的目的是释放您可能已分配的任何资源和内存(无论是在构造函数中还是在对象的整个生命周期中)。析构函数总是与类同名,前面加上波浪号(~):~Dog()。以下是说明这些操作的代码

#include <iostream>        // for cout
class Dog                 // begin declaration of the class
{
public:                    // begin public section
    Dog(int initialAge);   // constructor
    ~Dog();                // destructor
    int GetAge();          // accessor function
    void SetAge(int age);  // accessor function
    void Bark();
private:                   // begin private section
    int itsAge;            // member variable
};
  

Dog::Dog(int initialAge)
{
   itsAge = initialAge;
}
  
Dog::~Dog()                 // destructor, takes no action
{
}
  
// GetAge, Public accessor function
// returns value of itsAge member
int Dog::GetAge()
{
   return itsAge;
}
  
// Definition of SetAge, public
// accessor function
void Dog::SetAge(int age)
{
   // set member variable itsAge to
   // value passed in by parameter age
   itsAge = age;
}
 
void Dog::Bark()
{
   std::cout << "Bark.\n";
}
  
int main()
{
   Dog Lassie(5);
   Lassie.Bark();
   std::cout << "Lassie is a dog who is " ;
   std::cout << Lassie.GetAge() << " years old.\n";
   Lassie.Bark();
   Lassie.SetAge(7);
   std::cout << "Now Lassie is " ;
   std::cout << Lassie.GetAge() << " years old.\n";
   return 0;
}

输出

Bark.
Lassie is a dog who is 5 years old.
Bark.
Now Lassie is 7 years old.

一个实际应用

无论您使用的是 Visual Studio 2005 还是 2008,请下载 zip 文件,将所有文件解压到项目目录中一个新创建的文件夹中,然后双击解决方案文件。 enclosed application 可以根据您的需要进行扩展和更改。它是一个基于控制台的应用程序,用于管理实体员工记录,并提供雇佣或解雇员工,或列出任何过去、现在和现任员工的能力。还包括薪资和 ID 等其他属性。下面显示了 Employee.h 头文件。由于 Employee 类维护着有关员工的所有信息,因此其方法提供了一种查询和更改这些信息的方式。

// Employee.h

#include <iostream>
namespace Records {
  const int kDefaultStartingSalary = 30000;
  class Employee
  {
public:

      Employee();

      void     promote(int inRaiseAmount = 1000);
      void     demote(int inDemeritAmount = 1000);
      void     hire();     // hires or re-hires the employee
      void     fire();     // dismisses the employee
      void     display();  // outputs employee info to the console

      // Accessors and setters
      void          setFirstName(std::string inFirstName);
      std::string   getFirstName();
      void          setLastName(std::string inLastName);
      std::string   getLastName();
      void          setEmployeeNumber(int inEmployeeNumber);
      int           getEmployeeNumber();
      void          setSalary(int inNewSalary);
      int           getSalary();
      bool          getIsHired();
private: 
      std::string   mFirstName;
      std::string   mLastName;
      int           mEmployeeNumber;
      int           mSalary;
      bool          fHired;
    };
}

Employee.h 文件决定了 Employee 类的行为。Records 命名空间在整个程序中用于应用程序特定的代码。许多访问器提供了更改员工信息或查询员工当前信息的机制。数据成员声明为私有,以便代码的其他部分不能直接修改它们。访问器提供了修改或查询这些值的唯一公共途径。Employee.cpp 文件演示了 Employee 类方法的实现

// Employee.cpp

#include <iostream>
#include <string>
#include "Employee.h"

using namespace std;

namespace Records {

Employee::Employee()
{
    mFirstName = "";
    mLastName = "";
    mEmployeeNumber = -1;
    mSalary = kDefaultStartingSalary;
    fHired = false;
}
void Employee::promote(int inRaiseAmount)
{
    setSalary(getSalary() + inRaiseAmount);
}

void Employee::demote(int inDemeritAmount)
{
    setSalary(getSalary() - inDemeritAmount);
}
void Employee::hire()
{
    fHired = true;
}

void Employee::fire()
{
    fHired = false;
}
void Employee::display()
{
    cout << "Employee: " << getLastName() << 
         ", " << getFirstName() << endl;
    cout << "-------------------------" << endl;
    cout << (fHired ? "Current Employee" : "Former Employee") << endl;
    cout << "Employee Number: " << getEmployeeNumber() << endl;
    cout << "Salary: $" << getSalary() << endl;
    cout << endl;
}
// Accessors and setters

void Employee::setFirstName(string inFirstName)
{
    mFirstName = inFirstName;
}

string Employee::getFirstName()
{
    return mFirstName;
}

void Employee::setLastName(string inLastName)
{
    mLastName = inLastName;
}

string Employee::getLastName()
{
    return mLastName;
}

void Employee::setEmployeeNumber(int inEmployeeNumber)
{
    mEmployeeNumber = inEmployeeNumber;
}

int Employee::getEmployeeNumber()
{
    return mEmployeeNumber;
}

void Employee::setSalary(int inSalary)
{
    mSalary = inSalary;
}

int Employee::getSalary()
{
    return mSalary;
}

bool Employee::getIsHired()
{
    return fHired;
}

}

EmployeeTest.cpp 文件用于测试一些操作。如果您确信 Employee 类正常工作,那么您可以像所示那样注释掉代码

#include <iostream>
#include "stdafx.h"
#include "Employee.h"

using namespace std;

//int main (int argc, char** argv)
//{
//  cout << "Testing the Employee class." << endl;
//
//
//
//  emp.setFirstName("Dick");
//  emp.setLastName("Smith");
//  emp.setEmployeeNumber(71);
//  emp.setSalary(50000);
//  emp.promote();
//  emp.promote(50);
//  emp.hire();
//  emp.display();
//}

Database 类使用一个数组来存储 Employee 对象。一个名为 mNextSlot 的整数用作标记,以跟踪下一个未使用的数组槽。这不是数据结构的良好使用方式,因为数组是固定大小的——它不会动态调整大小

// Database.h

#include <iostream>
#include "Employee.h"

namespace Records {

  const int kMaxEmployees = 100;
  const int kFirstEmployeeNumber = 1000;
class Database
{
public:
    Database();
    ~Database();

    Employee& addEmployee(std::string inFirstName, std::string inLastName);
    Employee& getEmployee(int inEmployeeNumber);
    Employee& getEmployee(std::string inFirstName, std::string inLastName);
    void        displayAll();
    void        displayCurrent();
    void        displayFormer();
protected:
    Employee    mEmployees[kMaxEmployees];
    int         mNextSlot;
    int         mNextEmployeeNumber;
};
}

数据库有两个关联的常量。员工的最大数量是一个常量,因为记录保存在固定大小的数组中。因为数据库还将负责自动为新员工分配员工编号,所以一个常量定义了编号的起始位置。数据库通过提供名字和姓氏来提供一种添加新员工的便捷方式。为了方便起见,此方法将返回对新员工的引用。以下是相应的 Database.cpp 文件

// Database.cpp

#include <iostream>
#include <stdexcept>
#include <string>
#include "Database.h"

using namespace std;

namespace Records {

  Database::Database()
  {
    mNextSlot = 0;
    mNextEmployeeNumber = kFirstEmployeeNumber;
  }
  Database::~Database()
  {
  }
  Employee& Database::addEmployee(string inFirstName, string inLastName)
  {
    if (mNextSlot >= kMaxEmployees) {
      cerr << "There is no more room to add the new employee!" << endl;
      throw exception();
    }

    Employee& theEmployee = mEmployees[mNextSlot++];
    theEmployee.setFirstName(inFirstName);
    theEmployee.setLastName(inLastName);
    theEmployee.setEmployeeNumber(mNextEmployeeNumber++);
    theEmployee.hire();

    return theEmployee;
  }
  Employee& Database::getEmployee(int inEmployeeNumber)
  {
    for (int i = 0; i < mNextSlot; i++) {
      if (mEmployees[i].getEmployeeNumber() == inEmployeeNumber) {
    return mEmployees[i];
      }
    }

    cerr << "No employee with employee number " 
         << inEmployeeNumber << endl;
    throw exception();
  }

  Employee& Database::getEmployee(string inFirstName, string inLastName)
  {
    for (int i = 0; i < mNextSlot; i++) {
      if (mEmployees[i].getFirstName() == inFirstName &&
      mEmployees[i].getLastName() == inLastName) {
    return mEmployees[i];
      }
    }

    cerr << "No match with name " << inFirstName 
         << " " << inLastName << endl;
    throw exception();
  }
  void Database::displayAll()
  {
    for (int i = 0; i < mNextSlot; i++) {
      mEmployees[i].display();
    }
  }

  void Database::displayCurrent()
  {
    for (int i = 0; i < mNextSlot; i++) {
      if (mEmployees[i].getIsHired()) {
    mEmployees[i].display();
      }
    }
  }

  void Database::displayFormer()
  {
    for (int i = 0; i < mNextSlot; i++) {
      if (!mEmployees[i].getIsHired()) {
    mEmployees[i].display();
      }
    }
  }
}

下面显示了 Database 类的 DatabaseTest.cpp 文件,其中主源代码执行部分再次被注释掉。类最好单独进行测试

// DatabaseTest.cpp

#include <iostream>
#include "Database.h"

using namespace std;
using namespace Records;

/*
int main(int argc, char** argv)
{
  Database myDB;

  Employee& emp1 = myDB.addEmployee("Skippy", "Willy");
  emp1.fire();

  Employee& emp2 = myDB.addEmployee("Scott", "Tissue");
  emp2.setSalary(100000);

  Employee& emp3 = myDB.addEmployee("Jiffy", "Pop");
  emp3.setSalary(10000);
  emp3.promote();

  cout << "all employees: " << endl;
  cout << endl;
  myDB.displayAll();

  cout << endl;
  cout << "current employees: " << endl;
  cout << endl;
  myDB.displayCurrent();

  cout << endl;
  cout << "former employees: " << endl;
  cout << endl;
  myDB.displayFormer();
}
*/

用户界面

UserInterface.cpp 文件提供了一个基于菜单的显示界面。主函数是一个循环,显示菜单,执行选定的操作,然后重复所有操作。对于大多数操作,都定义了单独的函数。对于显示员工等更简单的操作,实际代码被放在相应的 case 中。

// UserInterface.cpp

#include <iostream>
#include <stdexcept>
#include <string>

#include "Database.h"

using namespace std;
using namespace Records;

int displayMenu();
void doHire(Database& inDB);
void doFire(Database& inDB);
void doPromote(Database& inDB);
void doDemote(Database& inDB);

int main(int argc, char** argv)
{
  Database employeeDB;
  bool done = false;

  while (!done) {
    int selection = displayMenu();

    switch (selection) {
    case 1:
      doHire(employeeDB);
      break;
    case 2:
      doFire(employeeDB);
      break;
    case 3:
      doPromote(employeeDB);
      break;
    case 4:
      employeeDB.displayAll();
      break;
    case 5:
      employeeDB.displayCurrent();
      break;
    case 6:
      employeeDB.displayFormer();
      break;
    case 0:
      done = true;
      break;
    default:
      cerr << "Unknown command." << endl;
    }
  }
}

int displayMenu()
{
  int selection;

  cout << endl;
  cout << "Employee Database" << endl;
  cout << "-----------------" << endl;
  cout << "1) Hire a new employee" << endl;
  cout << "2) Fire an employee" << endl;
  cout << "3) Promote an employee" << endl;
  cout << "4) List all employees" << endl;
  cout << "5) List all current employees" << endl;
  cout << "6) List all previous employees" << endl;
  cout << "0) Quit" << endl;
  cout << endl;
  cout << "---> ";

  cin >> selection;

  return selection;
}

void doHire(Database& inDB)
{
  string firstName;
  string lastName;

  cout << "First name? ";
  cin >> firstName;
  cout << "Last name? ";
  cin >> lastName;

  try {
    inDB.addEmployee(firstName, lastName);
  } catch (std::exception ex) { 
    cerr << "Unable to add new employee!" << endl;
  }
}

void doFire(Database& inDB)
{
  int employeeNumber;

  cout << "Employee number? ";
  cin >> employeeNumber;

  try {
    Employee& emp = inDB.getEmployee(employeeNumber);
    emp.fire();
    cout << "Employee " << employeeNumber 
         << " has been terminated." << endl;
  } catch (std::exception ex) {
    cerr << "Unable to terminate employee!" << endl;
  }
}

void doPromote(Database& inDB)
{
  int employeeNumber;
  int raiseAmount;

  cout << "Employee number? ";
  cin >> employeeNumber;

  cout << "How much of a raise? ";
  cin >> raiseAmount;

  try {
    Employee& emp = inDB.getEmployee(employeeNumber);
    emp.promote(raiseAmount);
  } catch (...) {
    cerr << "Unable to promote employee!" << endl;
  }
}

这是控制台应用程序及其选项的视图

Capture.JPG

© . All rights reserved.