📄 apa.htm
字号:
<B>Inline Functions</B></P>
<P>It's easy to confuse inline code, such as that in Listing A.3, with inline functions.
The compiler can choose to make any function an inline function, which provides tremendous
performance improvements for small functions. Because it can harm performance for
long functions, generally the compiler, not the programmer, makes the decision about
inlining. When you provide inline code, you are suggesting to the compiler that the
function be inlined. Another way to make this suggestion is to use the keyword inline
with the code outside the class declaration, like this:</P>
<PRE>inline void BankAccount::Withdraw(float amounttowithdraw)
{
balance -= amounttowithdraw;
if (balance < 0)
balance += amounttowithdraw; //reverse transaction
}</PRE>
<P>If this function will be called from other objects, don't inline it like this
in the .cpp file. Leave it in the header file or make a separate file for inline
functions, an .inl file, and #include it into each file that calls the member functions.
That way the compiler will be able to find the code.</P>
<P>The compiler might not inline a function, even though it has inline code (in the
class declaration) or you use the inline keyword. If you know what you're doing,
the __forceinline keyword introduced in Visual C++ 6 enables you to insist that a
function be inlined. (Notice that this keyword, like all nonstandard compiler keywords,
starts with two underscores.) Because this can cause code bloat and slow your application,
use this feature only when you have an almost complete application and are looking
for performance improvements. This is a Visual C++-only keyword that won't work with
other compilers.
<HR>
</BLOCKQUOTE>
<P>Perhaps you've already seen one of the other advantages of C++. Functions generally
require much fewer parameters. In another language, you might pass the account number
into each of these three functions to make it clear which balance you want to know
about or change. Perhaps you would pass the balance itself into a Withdraw() function
that checks the business rules and then approves or denies the withdrawal. However,
because these BankAccount functions are member functions, they have all the member
variables of the object to work with and don't require them as parameters. That makes
all your code simpler to read and maintain.</P>
<P>
<H3><A NAME="Heading6"></A>How Are Objects Initialized?</H3>
<P>In C, you can just declare a variable, like this:</P>
<P>
<PRE>int i;
</PRE>
<P>If you prefer, you can declare it and initialize it at the same time, like this:</P>
<P>
<PRE>int i = 3;
</PRE>
<P>A valid bank account needs values for its customer_id and account_num member variables.
You can probably start with a balance of 0, but what sensible defaults can you use
for the other two? More importantly, where would you put the code that assigns these
values to the variables? In C++, every object has an initializer function called
a <I>constructor</I>, and it handles this work. A constructor is different from ordinary
member functions in two ways: Its name is the name of the class, and it doesn't have
a return type, not even void. Perhaps you might write a constructor like the one
in Listing A.5 for the BankAccount class.</P>
<P>
<H4>Listing A.5  BankAccount Constructor</H4>
<PRE>BankAccount::BankAccount(char* customer, char* account, float startbalance)
{
strcpy(customer_id, customer);
strcpy(account_num, account);
balance = startbalance;
</PRE>
<PRE> }
</PRE>
<BLOCKQUOTE>
<P>
<HR>
<STRONG>TIP:</STRONG> strcpy() is a function from the C runtime library, available
to all C and C++ programs, that copies strings. The code in Listing A.5 copies the
strings that were passed to the constructor into the member variables.
<HR>
</BLOCKQUOTE>
<P>After writing the BankAccount constructor, you would add its declaration to the
class by adding this line to the class declaration:</P>
<P>
<PRE>BankAccount(char* customer, char* account, float startbalance);
</PRE>
<P>Notice that there is no return type. You don't need the class name and scope resolution
operator because you are in the class definition, and the semicolon at the end of
the line indicates that the code is outside the class declaration.</P>
<P>Now, when you declare a BankAccount object, you can initialize by providing constructor
parameters, like this:</P>
<P>
<PRE>BankAccount account("AB123456","11038-30",100.00);
</PRE>
<H3><A NAME="Heading7"></A>What Is Overloading?</H3>
<P>Imagine the banking application you are writing also deals with credit cards and
that there is a CreditCard class. You might want a GetBalance() function in that
class, too. In C, functions weren't associated with classes. They were all global,
and you couldn't have two functions with the same name. In C++ you can. Imagine that
you write some code like this:</P>
<P>
<PRE>BankAccount account("AB123456","11038-30",100.00);
float accountbalance = account.GetBalance();
CreditCard card("AB123456", "4500 000 000 000", 1000.00);
card.GetBalance();
</PRE>
<P>Most developers can see that the second line will call the BankAccount GetBalance()
function, whose full name is BankAccount::GetBalance(), and the fourth line will
call CreditCard::GetBalance(). In a sense, these functions don't have the same name.
This is one example of overloading, and it's a really nice thing for developers because
it lets you use a simple and intuitive name for all your functions, instead of one
called GetBankAccountBalance() and another called GetCreditCardBalance().</P>
<P>There's another, even nicer situation in which you might want two functions with
the same name, and that's within a single class. Take, for example, that BankAccount
constructor you saw a little earlier in this chapter. It might be annoying to pass
in a zero balance all the time. What if you could have two constructors, one that
takes the customer identifier, account number, and starting balance and another that
takes only the customer and account identifiers? You might add them to the class
declaration like this:</P>
<P>
<PRE>BankAccount(char* customer, char* account, float startbalance);
BankAccount(char* customer, char* account);
</PRE>
<P>As Listing A.6 shows, the code for these functions would be very similar. You
might feel that you need different names for them, but you don't. The compiler tells
them apart by their <I>signature</I>: the combination of their full names and all
the parameter types that they take. This isn't unique to constructors: All functions
can be overloaded as long as at least one aspect of the signature is different for
the two functions that have the same name.</P>
<BLOCKQUOTE>
<P>
<HR>
<STRONG>TIP:</STRONG> Two functions in the same class, with the same name, must differ
in the type or number of parameters. If they differ only in the return type, that
is not a valid overload.
<HR>
</BLOCKQUOTE>
<H4>Listing A.6  Two BankAccount Constructors</H4>
<PRE>BankAccount::BankAccount(char* customer, char* account, float startbalance)
{
strcpy(customer_id, customer);
strcpy(account_num, account);
balance = startbalance;
}
BankAccount::BankAccount(char* customer, char* account)
{
strcpy(customer_id, customer);
strcpy(account_num, account);
balance = 0;
</PRE>
<PRE> }
</PRE>
<H2><A NAME="Heading8"></A>Reusing Code and Design with Inheritance</H2>
<P>Maintainability and robustness, thanks to information hiding, are two of the features
that inspire people to switch to object-oriented programming. Another is reuse. You
can reuse other people's objects, calling their public functions and ignoring their
design decisions, by simply making an object that is an instance of the class and
calling the functions. The hundreds of classes that make up MFC are a good example.
C++ enables another form of reuse as well, called <I>inheritance</I>.</P>
<P>
<H3><A NAME="Heading9"></A>What Is Inheritance?</H3>
<P>To stick with the banking example, imagine that after you have BankAccount implemented,
tested, and working perfectly, you decide to add checking and savings accounts to
your system. You would like to reuse the code you have already written for BankAccount,
not just copy it into each of these new classes. To see whether you should reuse
by making an object and calling its functions or reuse by inheriting, you try saying
these sample sentences:</P>
<P>A checking account IS a bank account.</P>
<P>A checking account HAS a bank account part.</P>
<P>A savings account IS a bank account.</P>
<P>A savings account HAS a bank account part.</P>
<P>Most people agree that the IS sentences sound better. In contrast, which of the
following would you choose?</P>
<P>A car IS an engine (with some seats and wheels)</P>
<P>A car HAS an engine (and some seats and wheels)</P>
<P>If the IS sentences don't sound silly, inheritance is the way to implement your
design. Listing A.7 contains possible class declarations for the two new classes.</P>
<BLOCKQUOTE>
<P>
<HR>
<STRONG>TIP:</STRONG> The class reusing code in this way is called a <I>derived</I>
class or sometimes a <I>subclass</I>. The class providing the code is called the
<I>base</I> class or sometimes the <I>superclass</I>.
<HR>
</BLOCKQUOTE>
<H4>Listing A.7  CheckingAccount and SavingsAccount</H4>
<PRE>class SavingsAccount: public BankAccount
{
private:
float interestrate;
public:
SavingsAccount(char* customer, char* account,
float startbalance, float interest);
void CreditInterest(int days);
};
class CheckingAccount: public BankAccount
{
public:
Checking(char* customer, char* account, float startbalance);
void PrintStatement(int month);
</PRE>
<PRE>};
</PRE>
<P>Now, if someone makes a CheckingAccount object, he can call functions that CheckingAccount
inherited from BankAccount or functions that were written specially for CheckingAcount.
Here's an example:</P>
<P>
<PRE>CheckingAccount ca("AB123456","11038-30",100.00);
ca.Deposit(100);
ca.PrintStatement(5);
</PRE>
<P>What's terrific about this is what happens when someone changes the business rules.
Perhaps management notices that there are no service charges in this system and instructs
you to add them. You will charge 10 cents for each deposit and withdrawal, and subtract
the service charges on instruction from some monthly maintenance code that another
developer is writing. You open up BankAccount.h and add a private member variable
called servicecharges. You set this to zero in the constructor and increase it by
0.1 in both Deposit() and Withdraw(). Then you add a public function called ApplyServiceCharges()
that reduces the balance and resets the charges to zero.</P>
<P>At this point in most other languages, you'd have to repeat all this for CheckingAccount
and SavingsAccount. Not in C++! You have to add a line to the constructors for these
classes, but you don't change anything else. You can reuse your changes as easily
as you reused your BankAccount class in the first place.</P>
<P>
<H3><A NAME="Heading10"></A>What Is Protected Access?</H3>
<P>Without writing all of CheckingAccount::PrintStatement(), you can assume it will
need to know the balance of the account. To spare any hard-to-read code that would
put that number onscreen, consider this line of code:</P>
<P>
<PRE>float bal = balance;
</PRE>
<P>This line, inside CheckingAccount::PrintStatement(), will not compile. balance
is a private member variable, and no code can access it, other than code in BankAccount
member functions. Though this probably seems outrageous, it's actually very useful.
Remember the possible design decision that would change the type of balance from
float to int? How would the BankAccount programmer know all the classes out there
that inherited from BankAccount and rely on the type of the balance member variable?
It's much simpler to prevent access by any other code. After all, CheckingAccount::PrintStatement()
can use a public function, GetBalance(), to achieve the desired result.</P>
<P>If you want to grant code in derived classes direct access to a member variable,
and you're confident that you will never need to find all these classes to repeat
some change in all of them, you can make the variable protected rather than private.
The class declaration for BankAccount would start like this:</P>
<P>
<PRE>class BankAccount
{
protected:
float balance;
private:
char[8] customer_id;
char[8] account_num;
// ...
};
</PRE>
<H3><A NAME="Heading11"></A>What Is Overriding?</H3>
<P>At times, your class will inherit from a base class that already provides a function
you need, but the code for that function isn't quite what you want. For example,
BankAccount might have a Display() function that writes onscreen the values of the
three member variables: customer_id, account_num, and balance. Other code could create
BankAccount, CheckingAccount, or SavingsAccount objects and display them by calling
this function. No problem. Well, there is one little problem: All SavingsAccount
objects have an interestrate variable as well, and it would be nice if the Display()
function showed its value, too. You can write your own code for SavingsAccount::Display().
This is called an <I>override</I> of the function from the base class.</P>
<P>You want SavingsAccount::Display() to do everything that BankAccount::Display()
does and then do the extra things unique to savings accounts. The best way to achieve
this is to call BankAccount::Display() (that's its full name) from within SavingsAccount::Display().
Besides saving you time typing or copying and pasting, you won't have to recopy later
if someone changes the base class Display().</P>
<P>
<H3><A NAME="Heading12"></A>What Is Polymorphism?</H3>
<P>Polymorphism is a tremendously useful feature of C++, but it doesn't sneak up
on you. You can use it only if you are using inheritance and pointers, and then only
if the base class in your inheritance hierarchy deliberately activates it. Consider
the code in Listing A.8.</P>
<BLOCKQUOTE>
<P>
<HR>
<STRONG>TIP:</STRONG> If you haven't worked with pointers before, you may find the
&, *, and -> operators used in this code example confusing. & means <I>address
of</I> and obtains a pointer from a variable. * means <I>contents of</I> and uses
a pointer to access the variable it points to. -> is used when the pointer has
the address of an object rather than an integer, float, or other fundamental type.
<HR>
</BLOCKQUOTE>
<H4>Listing A.8  Inheritance and Pointers</H4>
<PRE>BankAccount ba("AB123456","11038-30",100.00);
CheckingAccount ca("AB123456","11038-32",200.00);
SavingsAccount sa("AB123456","11038-39",1000.00, 0.03);
BankAccount* pb = &ba;
CheckingAccount* pc = &ca;
SavingsAccount* ps = &sa;
pb->Display();
pc->Display();
ps->Display();
BankAccount* pc2 = &ca;
BankAccount* ps2 = &sa;
pc2->Display();
ps2->Display();
</PRE>
<PRE>};
</PRE>
<P>In this example, there are three objects and five pointers. pb, pc, and ps are
straightforward, but pc2 and ps2 represent what is often called an <I>upcast</I>:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -