Since C ++ is almost forgotten, I have switched to the latest C ++ Primer version and intend to learn more about the C ++ language. The fifth version of C ++ Primer has been "restructured". It integrates the C ++ 11 standards and lists them all in the book.
In the course of learning, I will compare C ++ with pure object-oriented languages such as Java and C #. Some insights in the middle only represent my personal opinions, there may be some disputes.
After almost finishing the entire Primer C ++ and understanding the C ++ 11 standard, I felt like C ++ is a flexible, powerful, and inefficient language.
The so-called "flexibility" and "power" refer to the development efficiency compared with Java and C #, while "Low Efficiency" refer to the development efficiency relative to C # and Java, rather than program running efficiency. At the same time, C ++ has many "Conventions" and these rules cannot be fully written. Therefore, it is best to have a C ++ manual at hand during programming.
I will list several flexible areas of C ++:
1. Operator overloading, copy control, implicit conversion, etc.
In C ++, almost all operations can be overloaded, such as +,-, new, and delete. Even if you use = to assign values, a function is actually running. If you do not define such a function, the compiler will use its compositing version (that is, the default version ). The copy of the default version is to copy the member value. If the Member is a pointer, only the copy address is used. For example, if there is A Class a instance A, the member contains a pointer ptr, after copying with the default version, such as A B = ;, the ptr in B and a points to the same address. If one of a and B points to an object that is parsed, when another object is parsed, an error occurs (C ++ Primer version 5, P447). Therefore, the object to be parsed also needs a copy and value assignment operation.
The above example shows that, as a class designer, you must take all the situations into account and know the memory allocation well. Otherwise, the class you designed may have problems.
Another example is implicit conversion, such as std: string str = "hello", which converts the C-style const char * to std: string, this kind of conversion is very direct in our understanding, but sometimes this kind of conversion is not only hard to understand, it will also cause ambiguity and cause compilation failure.
In my opinion, Operator Overloading is not necessary for a language. In C ++, we can easily think that adding two std: strings is equivalent to concatenating two strings. in Java and C, it is forbidden to overload the operator (C # has an exception, that is, it reloads the String class + by default), the reason I guess may be to prevent the program structure from being too messy. In fact, I'm not used to overloading too many operators unless I have to reload them (for example, when using a map class, I have to reload them <) because it does make it more difficult to read the code. For example, I want to construct a class in C ++ and C # (or Java) respectively. They have the "addition" operator, which may be like this in C ++:
class CanAdd{public: int value; CanAdd& operator + (const CanAdd a){ value += a.value; return *this; }};
For some template classes that require the + operation, it is obviously no problem to pass this class into the template class. However, the template class cannot guarantee that the passed classes must have the + operator. In C # or Java, we prefer this (Java code ):
abstract class ICanAdd{int value;abstract ICanAdd add(ICanAdd item);}class CanAdd extends ICanAdd{public ICanAdd add(ICanAdd item){value += item.value;return this;}}
The abstract class ICanAdd explicitly contains the add method. All ICanAdd objects can call add. Their base class determines how to call add. In C # and Java, this type is safe to be used in generics, because we can constrain that this generic class must inherit ICanAdd to ensure that it can call add, the template class cannot be guaranteed in this way. The Compiler only knows the problem at the moment of template instantiation. If there is a problem, it may face a lot of Link errors.
In addition, the meaning of excessive use of reloads is vague. For example, std: cout <std: endl;, cout is a member of the std namespace, however, endl is a function in std. I personally think it would be too hard to understand if a program is filled with such "conventions" operator.
2. pointers, values, references, and Polymorphism
The difficulty of C ++ lies in its memory management. In C # and Java, there is a sound garbage collection mechanism, in addition to the basic types (and struct in C ), the transfer method is reference (C # can also reference a value type variable ). Different symbols, such as * and &, have different meanings in different positions, and are easy to confuse. For example, instance * t indicates whether the instance is multiplied by t or a pointer t pointing to the instance class. This is particularly evident in the template. typename must be added for types that contain the domain operators, such as typename CLASS. : Member, because the compiler does not know whether member is a type or a member variable.
Left-value reference, right-value reference, value transfer, and reference transfer are major challenges for programmers. This is especially true when we use keywords such as decltype, auto, and const. For example, if there is a statement int a, the type returned by decltype (a) is int, And the type returned by decltype (a) is int &. These rules can only be remembered in practice.
C ++ is different from C # And Java when defining variables. For example, A Class A has been defined. In C ++, a; indicates that A has been defined (space allocated). If you only want to declare it, either extern A a; or A * ;. In C # and Java, A a; is always A declaration, unless you use a = new A (), which indicates that a has been instantiated. Using uninstantiated variables causes an exception.
Although C ++ is an "Object-Oriented" language, we cannot directly operate on it. Example:
#include
using namespace std;class Base {public: virtual void Call(){ std::cout << "Base func called" << std::endl; }};class Derived : public Base {public: void Call(){ std::cout << "Derived func called" << std::endl; }};void call(Base b){ b.Call();}int main(int argc, const char * argv[]){ Base base; Derived derived; call(base); call(derived); return 0;}
Running result:
Base func called
Base fund called
According to the polymorphism principle, the call function calls the member function Call. Why is the base class Call still called for the derived object? The reason is that C ++ polymorphism is only reflected in references and pointers: When you pass in a derived to call, the compiler actually copies a parameter B According to the Base copy constructor, B is of the Base type, and B is called again. call (), the Call must be Base. call. To prevent Base from calling the copy constructor, the parameters we pass to call are either Base & or Base *. If we want to use "polymorphism", the object must be a reference or pointer, because assigning values to them will not trigger the copy structure.
3. interfaces and multi-inheritance C ++ do not have the concept of interfaces, but have the concept of inheritance. Inheritance is a major feature of object-oriented programming. C ++ supports multi-inheritance and Inheritance of various access permissions. C # and Java do not support multi-inheritance. They can only inherit from one class, but can inherit from multiple interfaces. One of the reasons why C ++ supports multi-inheritance is that C ++ does not have the concept of "interface. An interface is a completely abstract class. It only declares the function bodies and cannot implement them. Abstract classes can be used in C ++ to implement interfaces. Therefore, C ++ must support multiple inheritance. For example, a puppy can run and eat. It is an Animal. In C ++, it can be declared as follows: class Dog: public Animal, public CanEat, public CanRun, and in Java, it is declared as follows: class Dog extends Animal implements CanEat, CanRun, and CanEat, CanRun must be interfaces -- they cannot implement their own member functions, it can only be declared. Multi-inheritance can cause some trouble. For example, if the parent class inherits the same class, the derived class will inherit the class twice. In this case, the virtual base class is used to solve the problem. In addition, multiple inheritance also causes duplicate names. It is advantageous to separate an "interface" from a class. It clearly shows a certain method in the class. If you use a pure abstract class to replace the interface, there may be obfuscation like "Is a", that Is, -- Dog Is Animal and CanEat and CanRun, and the correct view should be: Dog Is animal, and has the capabilities such as CanEat and CanRun. So now, I have a habit: A class can inherit at most one class that contains members or implements Multiple member functions, and can inherit multiple pure abstract classes (excluding Member fields, and only contains the declared class of the member function ). Of course, this habit may have limitations in some cases. 4. typedef and # define. Let's talk about define first. Macro definition is an effective way to improve program efficiency because it consumes time in the compiler. We can write a lot of common functions into macro definitions, so that when running it, the parameter will not be reversed and stack-to-stack impact on efficiency, in C ++, functions can also be defined as inline to improve efficiency. Define can improve the readability of the Code to a certain extent, but it may be confusing. Therefore, the define function in C # is restricted, that is, define cannot define a macro as a value. It can only be defined, and whether or not it is defined, the most common purpose is # define DEBUG for some debugging. Typedef, and using commonly used in C ++ 11, are alias for type. It has two main functions in my opinion: one is to improve the readability of the Code. For example, the readability of a string type is certainly higher than that of const char *, and the readability of A strVectorWithVector is certainly better than that of a vector. > High: Like define, typedef in the sky also reduces the readability of the Code. The second role is to increase the portability of the software. On Machine A, int occupies 8 bytes, and on machine B, int occupies 16 bytes. The int A defined on machine a may be changed to long a on machine B, this is a huge workload and prone to errors. Therefore, on machine A, use typedef to name int as MyInt. On machine B, change the type corresponding to MyInt to long to change all types. However, such type migration should rarely occur in C # and Java, because they all have their own "virtual machines "--. NET Library has CLR, Java has Java virtual machine, they will handle such compatibility issues on different terminals.
5. Memory Management memory management is a nightmare for C ++ programmers. Although shared_ptr in boost is introduced in the standard library, shared_ptr still does not have a garbage collection mechanism, and it is difficult to use shared_ptr, unlike garbage collection, C ++ designers may think that all control of programs should be handed over to programmers, but not all programmers (or their bosses) are so patient, A sound memory management mechanism may take a considerable amount of time. ObjectiveC introduced the autorelease garbage collection mechanism, but I don't think C ++ will have a garbage collection mechanism before the next standard. In C # and Java, the collection of a class is irregular, but you can define what they should do during the collection. In C ++, you must write your own analysis engine.
The above lists some areas where C ++ is very flexible, and there are many other areas where it is flexible, such as in the iterator -- C ++, if you want to use range, this class must have begin (), end (), and they return an iterator. This iterator can be a class defined by itself. It must implement the * operator ,! = Operator and ++ operator. The standard library provides a variety of complex iterators, And the foreach "statement" has been implemented in QT before range for came out ". In C # and Java, to use foreach, the class must implement the iterator interface. C ++ does not have the concept of "assembly", so there is no reflection mechanism. Using decltype is already a "breakthrough. In C ++, an object is a piece of memory. in C # and Java, we can use the GetType or. class method to obtain the type information.
I have to say that C ++ has a lot of "manual regulations". Most operations and memory management need to be implemented by myself, and a clear understanding of the computer structure is required to write a good program. C ++ is so complicated that it will feel a sense of accomplishment!