📄 c++.txt
字号:
typesetting notes: @I(...) surrounds Italic text, and @b(...)
is for bold face. Due to the heave yse of programming keywords
in the text, it would be impossible to read without some
representation for the alternate font. You can use a search and
replace to convert these to your preferred format (like ` ' or [ ]
for reading online) or the printer control codes you need or the
correct commands to import into your word processor.
This is the first C++ lesson. The subject is Operators. This
is adapted from material in my upcoming book. Please direct any
comments and criticsm to me on the forum or by email.
--John
C++ Operators
Copyright 1990 by John M. Dlugosz
Say you have defined a @I(complex) type to work with complex numbers.
In C, to add two complex numbers you would have to have a
function like @I(complex add(complex,complex)). An expression of any
size would end up looking more like LISP than C. In C++, you can
overload operators. In this case, I would much rather say @I(c=a+b;)
than @I(c=add(a,b)). Naturally, I can do exactly that.
The name of an operator is the reserved word @I(operator) followed by
the symbol of the operator being referred to. In this case,
@I(operator+) is what I am after. @I(operator+) can be treated just like
any other function name. That is how I define the function:
@I(complex operator+ (complex,complex);) It is exactly like @I(add())
except the name of the function is now @I(operator+). I can use it
just like I did above, as @I(c=operator+(a,b);) That is hardly an
improvement! But I can also use the operator in its natural
operator syntax, as @I(c=a+b;)
The operators are just like functions with a special name, but
some restrictions apply. You can only define those operators
that exist, with the correct number of parameters. The type of
the parameters are much more flexible, and is the whole point of
defining them. However, at least one parameter to an operator
function must be of a class type. So, you could not define @I(void
operator+ (char*, char*);) because all arguments are of built-in
types.
Operators can be member functions. In this case, the first
argument is the receiver and the function is declared with one
less argument than it actually has. So if I defined operator+ as
a member, I would have @I(complex complex::operator+ (complex);) It
can be used as @I(c=a.operator+(b);) as a member function with a
funny name, or as the normal infix operator. In the infix
syntax, the left argument is taken as the object being operated
on.
@SECTION(available operators)
These are the operators that can be overloaded:
new delete @I(storage allocation)
+ - * @I(both unary and binary forms
& @I(unary and binary, unary is special)
= @I(assignment operator is special)
/ % ^ | ~ ! < > << >> ==
!= += -= *= /= %= ^= &= |= ->*
&& || , @I(have left to right evaluation)
++ -- @I(prefix and postfix, special way to distinguish
-> () [] @I(special in various ways)
In addition, @I(conversion operators) are also defined with the
operator keyword. Conversion operators are covered in a later
chapter.
Some operators can be used in more than one way. For example,
operator- can be used as unary (one argument) or binary (two
arguments). All operators keep the same order of precedence when
they are redefined.
@SECTION(defining an operator)
Back to the example of complex addition. Here is a @I(complex) class
that has operator+= and operator+ defined for complex numbers.
@BEGIN(LISTING)
//example using overloaded operators to define arithmetic
//on complex numbers.
class complex {
double x, y;
public:
complex (double xx, double yy) { x=xx; y=yy; }
complex& operator+= (const complex& b);
friend complex operator+ (const complex& a, const complex& b);
};
complex& complex::operator+= (const complex& b)
{
x += b.x;
y += b.y;
return *this;
}
complex operator+ (const complex& a, const complex& b)
{
complex temp= a;
return temp += b;
}
@END(LISTING)
The operator+= is defined as a member function. It takes one
argument in addition to the implicit @I(this). The line @I(return temp
+= b;) is equivalent to @I(return temp.operator+=(b);). Remember,
operators defined as members have one less argument defined than
they actually take.
@SECTION(standard input and output)
The @I(stream) library in C++ uses overloaded operators for standard
input and output. The operator<< is used as a "put to", and
operator>> is used as a "get from". There are classes @I(ostream)
and @I(istream) to handle output and input. Functions such as:
@BEGIN(LISTING)
ostream& operator<< (ostream&, int);
ostream& operator<< (ostream&, const char*);
istream& operator>> (istream&, int&);
istream& operator>> (istream&, char*&);
@END(LISTING)
Are used to handle input and output. Each operator returns the
first argument as the function result, so they can be chained
together. The standard input and output are available as
variables @I(cin) and @I(cout).
@BEGIN(LISTING)
cout << "hello world!\n";
cout << "the answer is:" << x;
cin >> x >> y; //read values into x and y;
@END(LISTING)
You can define your own output and input operators to operate on
your own types.
@BEGIN(LISTING)
ostream& operator<< (ostream& o, const complex& c)
{
//I'm a friend, and have access to c.x and c.y
o << '(' << c.x << ',' << c.y << ')';
return o;
}
istream& operator>> (istream& i, complex& c)
{
double x, y;
char c;
i >> c >> x >> c >> y >> c;
c= complex(x,y);
return o;
}
@END(LISTING)
The input operator is a little overkill for such a simple
structure-- you could have made it a friend and just read in the
x and y components directly. But this illustrates a more
general technique. You read the data needed to create an object,
and then call a constructor to put it together.
In general, use @I(cin >> x;) to read a value, and @I(cout << x;) to
write a value. That is all you have to remember until you get to
the chapter on streams.
@SECTION(operator=)
Normally when you write an assignment such as @I(x=y;) the value of y
is moved to x simply by copying the bits. The assignment
operator lets you define how assignment will take place instead.
Consider a class that contains pointers. When you assign one
instance to another, what you really want is a "deep copy" where
the pointers are also duplicated to give the new copy its own.
@BEGIN(LISTING)
class C {
char* name;
int age;
public:
// other members omitted for example...
C& operator= (C&); //assignment operator
};
C& C::operator= (C& x)
{
age= x.age; //copy each element
free (name); //free `name' before overwriting it!
name= strdup (x.name); //make unique copy
return *this;
}
@END(LISTING)
The assignment operator must be a member. It should be used to
provide assignment, and not for anything else. The parameter can
be any type though. In fact, you can have multiple assignment
operators defined for a class, so you can assign various things
to it.
@BEGIN(LISTING)
class String {
char* s;
int len;
public:
String& operator= (String&);
String& operator= (const char*);
String& operator= (char);
};
@END(LISTING)
In class C, notice that the operator= returns a @I(C&) and in fact
returns @I(*this). This is common practice, and allows chaining of
assignment and the use of the assignment in a larger expression
as with the built-in use. The built-in assignment operator
returns the first argument as its result, so you can do things
like @I(foo(a=b+1);) and @I(a=b=c;). It is common practice to continue
this tradition by returning @I(*this) from operator=.
Assignment in C does not return an lvalue, but my supplied
operator= does. In C++, this has been extended to the built-in
definitions as well. This means that you could say something
like @I(p= &(c=b);) which takes the address of the return value from
assignment, even for built-in types. You could not do that in C:
you would get the error @I(argument to `&' must be an lvalue).
You can define operator= to return anything you like. Generally
it returns @I(*this) or has no return value (defined as void), but
there may be reasons to do otherwise.
The operator= is unique in that it is not inherited. Actually,
it is inherited in a special way. If you have a class that has
members or base classes that have an operator= defined, and do
not provide an operator= in this class, the compiler generates
one for you that calls operator= for those pieces of the class
that have an operator= defined, and copies the rest.
@BEGIN(LISTING)
// example of derived class needing operator=
class D {
int x;
C y; // C from above example
public:
D& operator= (D&);
};
D& D::operator= (D& second)
{
x= second.x; //normal copy
y= second.y; //calls C::operator=()
return *this;
}
@END(LISTING)
Since class D contained a member that should not be copied in the
plain way, it defined an operator= that copied it correctly by
calling the operator= on that member. However, if I had not
written @I(D::operator=()) then it would still have done the same
thing! This is sometimes called the Miranda rule: if you do not
write an operator=, one will be provided for you. Beware though that
if you do write an operator= for a class, make sure you indeed
copy everything in it.
@b(Version Notes)
In C++ versions prior to 2.0, there was no Miranda rule. The
operator= was not inherited at all. If you did not provide a new
operator= in a derived class, or did not write an operator= in a
class with members that need it, you get a bit-for-bit copy
anyway.
@SECTION(operator[])
The operator[] can be overloaded, and it is one of the special
ones. First of all, the syntax is different. Writing @I(x[y];) will
call @I(x.operator[](y);) Second, it must be a member function.
Here is an example of a vector class that uses this operator.
This implements an array that grows as needed to accommodate new
elements.
@BEGIN(LISTING)
#include <iostream.h>
typedef int eltype;
class vector {
eltype* elements;
int capacity;
public:
vector (int startsize= 10);
~vector() { delete[capacity] elements; }
int size() { return capacity; }
eltype& operator[] (int index);
};
vector::vector (int startsize)
{
capacity= startsize;
elements= new eltype[startsize];
}
eltype& vector::operator[] (int index)
{
if (index >= capacity) { //out of bounds
eltype* new_array= new eltype[index+1];
for (int loop= 0; loop < index; loop++)
new_array[loop]= elements[loop];
delete[capacity] elements;
elements= new_array;
capacity= index+1;
}
return elements[index];
}
main()
{
vector a;
for (;;) {
char c;
int index, value;
cout << "operation (r,w,q)? ";
cin >> c;
switch (c) {
case 'q': goto done;
case 'r': //read test
cout << " index ";
cin >> index;
if (index < 0 || index >= a.size())
cout << "index out of range.";
else cout << "contains " << a[index];
break;
case 'w': //write test
cout << " index and value ";
cin >> index >> value;
a[index]= value;
break;
case 's': //size?
cout << "array holds " << a.size() << " elements.";
break;
case 'l': //list them
for (index= 0; index < a.size(); index++)
cout << "\n [" << index << "] " << a[index];
break;
}
cout << '\n';
}
done:
cout << "program finished.\n";
}
@END(LISTING)
The operator[] returns an element of the array, and returns it by
reference. This allows it to be used on the left hand side of an
assignment, as seen in the write test in the main program.
This is a typical application-- to access a collection of
elements. The parameter to operator[] can be of any type though.
For example, an associative array could associate strings and
numbers. Index it with a number and it returns the string, and
index it with a string and it returns the number! Interesting?
Here is the code:
@BEGIN(LISTING)
#include <iostream.h>
#include <string.h>
/* associative array of strings */
typedef char* eltype;
class asa { //associative string array
eltype* elements;
int capacity;
public:
asa (int startsize= 10);
~asa() { delete[capacity] elements; }
int size() { return capacity; }
eltype& operator[] (int index);
int operator[] (eltype s); //the 'backwords' version
};
asa::asa (int startsize)
{
capacity= startsize;
elements= new eltype[startsize];
}
int asa::operator[] (eltype s)
{
for (int loop= 0; loop < capacity; loop++)
if (!strcmp(s,elements[loop])) return loop; //found it
//did not find it-- add it
(*this)[loop]= strdup(s);
return loop;
}
eltype& asa::operator[] (int index)
{
if (index >= capacity) { //out of bounds
eltype* new_array= new eltype[index+1];
for (int loop= 0; loop < index; loop++)
new_array[loop]= elements[loop];
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -