When you have a class/struct that has a construct that can take exactly one parameter, the compiler can try to use that constructor in an implicit conversion. To better explain, consider the following example:
1 2 3
|
void foo( string s ) {
cout << "foo() called with parameter " << s << endl;
}
|
There are a couple of ways you can call this function. One way is this:
|
foo( string( "Hello World" ) );
|
It works because you are passing a string to foo, which is exactly what foo takes. But you can also call the function this way:
Why is that? After all, "Hello World" is a const char*, not a string. The reason is because the string class has the following constructor (simplifying things a bit here to demonstrate the point):
1 2 3 4
|
class string { // Ok, it's really a templated class called basic_string...
public:
string( const char* s ) { ... }
};
|
Note that the constructor is not declared explicit.
So in the example
the compiler sees that you are calling foo() with a const char*. The compiler also sees that there is no function foo() that takes a const char*, but there is one that takes a string. So the compiler then checks to see if a const char* can be implicitly converted to a string. The answer is yes, because string has a constructor that takes exactly one parameter, a const char*, and the constructor is not declared explicit. So what the compiler really does is it constructs a temporary string object from your const char* and then calls the function with the temporary.
Now this implicit conversion stuff seems convenient, but in huge projects you might not want the compiler to do that. That's what the explicit keyword does. It tells the compiler that it may not use that constructor in an implicit conversion. To use the example above:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
class string {
public:
// Suppose this were declared explicit:
explicit string( const char* s ) { ... }
};
// Then this compiles and works:
string s = "Hello World";
foo( s );
// And this compiles and works:
foo( string( "Hello World" ) );
// But this would NOT compile:
foo( "Hello World" );
|
Now, on to your second question. I'm not sure if you are unsure about the "const", the "&", the ordering (string const& instead of const string&), or the default parameter. So I'll try to answer all of them.
The ampersand (&) is a reference. A reference can be thought of as a pointer but you don't use pointer syntax to access it. For example:
1 2 3 4 5 6 7 8 9 10 11 12
|
string s = "Hello World"; // An ordinary variable
string* ps = &s; // A pointer to a string
string& rs = s; // A reference to a string
// You use the "." syntax to access members
cout << "Length is " << s.length() << endl;
// You use the "->" syntax to access members through a pointer
cout << "Length is " << ps->length() << endl;
// You use the "." syntax to access members through a reference
cout << "Length is " << rs.length() << endl;
|
So why use references at all? Well, you know that sometimes you don't want to copy a variable onto the stack when you call a function because copying the object is expensive (it might be a big array, for example). So you're taught instead to pass it as a pointer, because it's easy to put a pointer on the stack. But pointers usually aren't the right approach in C++ (they may be in other languages such as Pascal since those languages don't have references). Rather, you should use references. There are many reasons why references are better than pointers, but here are a few to highlight the major reasons (IMHO):
1) Pointers can be NULL, but references cannot. You therefore have to
check pointers before dereferencing, but not references. Although
checking pointers is easy, often times handling the error condition is
not (particularly in large applications).
2) Pointers and non-pointers don't mesh in generic programming because
the access syntax (. vs. ->) is different. If you haven't done template
(generic) programming, you might not understand why this is important.
Anyway, copying strings is theoretically expensive (in practice, most if not all STL string implementations use copy-on-write semantics which makes string copies cheap, but nothing in the C++ standard mandates that implementation, and it is subject to change particularly if the C++ committee ever decides to fully support threaded programming). So I want to pass the string by reference.
Now, const. One weakness of passing pointers to data rather than copying the data on the stack is that the function receiving the data could modify it, and the caller of the function might not expect that. By declaring a parameter to a function as a "const reference", it means that the caller will pass a pointer to the data to the function, but the function is guaranteeing not to modify the data being pointed to. Here's a few examples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
void foo1( string s ) {
s = "Hello World";
}
void foo2( string& s ) {
s = "Hello World";
}
void foo3( const string& s ) {
s = "Hello World";
}
void foo4( string* s ) {
*s = "Hello World";
}
void foo5( const string* s ) {
*s = "Hello World";
}
int main() {
string s1 = "Goodbye World";
string s2 = s1;
string s3 = s1;
string s4 = s1;
string s5 = s1;
// s1 is copied onto stack, so foo1() modifies the copy. s1 is unchanged.
foo1( s1 );
// s2 is passed via pointer, therefore foo2() modifies s2!
foo2( s2 );
// foo3() won't even compile, because const says that foo3()
// won't modify the parameter, but s3 tries to anyway.
foo3( s3 );
// Same as s2, except foo4() needs to use pointer syntax
foo4( &s4 );
// Same as s3 - won't compile.
foo5( &s5 );
}
|
Ok, now in the above functions, I declared the parameter as "const string&" whereas in my original reply I used "string const&". They are in fact saying the same thing.
Lastly, default parameter. Take this example:
1 2 3
|
void foo( int x = 4 ) {
cout << "x = " << x << endl;
}
|
What this says is that when the programmer calls function foo(), they can specify the value of x as usual (ie, foo( 10 )) or the programmer needn't specify the value at all! In the latter case, if the user calls foo() this way: foo(), the declaration of function foo() says that the compiler should assume the programmer called it with value 4.
You can default parameters to any functions, including constructors, however there are a few restrictions. You can, for example, have multiple default parameters:
1 2 3
|
void foo( int x = 3, int y = 5, float z = 3.14 ) {
cout << x << y << z << endl;
}
|
And then, any of these calls will compile:
1 2 3 4
|
foo( 1, 2, 3.0 ); // x=1, y=2, z=3.0
foo( 2, 4 ); // x=2, y=4, z=3.14
foo( 8 ); // x=8, y=5, z=3.14
foo(); // x=3, y=5, z=3.14
|
The trick is that once you make one parameter a default parameter, every parameter after it must also have a default. For example, the following would not compile:
|
void foo( int x = 5, int z ) {}
|
Since x has a default value, every parameter after it must also have a default value, and z does not.
In my original reply, I defaulted all of the string parameters to the value "string()" which is nothing more than a default constructed string. There is a very slight but important difference between the following two lines of code:
1 2
|
string s; // Default constructor for s is called
string s1 = ""; // string( const char* ) constructor is called
|
It's subtle, and it will almost never make a functional difference in a program, but the default constructed string is slightly more efficient.
Let me know if you have any more questions. And I apologize if I explained things you already know.