-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 360 KB
/
content.json
1
{"meta":{"title":"うさぎの手帳","subtitle":null,"description":null,"author":"Noah Bishop","url":"https://NoahBishop.github.io"},"pages":[{"title":"donate","date":"2018-12-20T15:13:05.000Z","updated":"2023-04-20T11:21:42.035Z","comments":false,"path":"donate/index.html","permalink":"https://noahbishop.github.io/donate/index.html","excerpt":"","text":"","keywords":"ありがとうございます~"},{"title":"comment","date":"2021-12-20T15:13:48.000Z","updated":"2023-04-20T11:24:28.233Z","comments":true,"path":"comment/index.html","permalink":"https://noahbishop.github.io/comment/index.html","excerpt":"","text":"Welocme 有什么想说的可以给我留言哦!","keywords":"留言板"},{"title":"links","date":"2018-12-19T15:11:06.000Z","updated":"2023-04-20T11:28:10.102Z","comments":true,"path":"links/index.html","permalink":"https://noahbishop.github.io/links/index.html","excerpt":"","text":"","keywords":"友人帐"},{"title":"rss","date":"2018-12-20T15:09:03.000Z","updated":"2021-11-09T14:13:18.000Z","comments":true,"path":"rss/index.html","permalink":"https://noahbishop.github.io/rss/index.html","excerpt":"","text":""},{"title":"tags","date":"2018-12-12T14:14:16.000Z","updated":"2023-04-21T08:42:45.734Z","comments":true,"path":"tags/index.html","permalink":"https://noahbishop.github.io/tags/index.html","excerpt":"","text":""},{"title":"about","date":"2021-12-12T14:14:36.000Z","updated":"2023-04-22T15:23:19.285Z","comments":false,"path":"about/index.html","permalink":"https://noahbishop.github.io/about/index.html","excerpt":"","text":"[さくら荘のうさぎ] 与 有希 ( 真(ま)中(なか) ) 对话中... bot_ui_ini()","keywords":"关于"},{"title":"music","date":"2021-12-18T15:14:28.000Z","updated":"2023-02-04T12:52:09.720Z","comments":false,"path":"music/index.html","permalink":"https://noahbishop.github.io/music/index.html","excerpt":"","text":"","keywords":"喜欢的音乐"},{"title":"theme-sakura","date":"2021-12-18T14:53:25.000Z","updated":"2023-04-20T11:22:08.758Z","comments":false,"path":"theme-sakura/index.html","permalink":"https://noahbishop.github.io/theme-sakura/index.html","excerpt":"","text":"Hexo主题Sakura修改自WordPress主题Sakura,感谢原作者Mashiro 现在是基于Hexo主题Sakura的二次修改,感谢Hexo主题Sakura作者honjun","keywords":"Hexo 主题 Sakura 🌸"}],"posts":[{"title":"Setting up Qt Projects with CMake and Linking External","slug":"essay_01_use cmake settting up Qt project","date":"2022-07-01T13:52:45.000Z","updated":"2023-04-24T16:14:34.225Z","comments":true,"path":"2022/07/01/essay_01_use cmake settting up Qt project/","link":"","permalink":"https://noahbishop.github.io/2022/07/01/essay_01_use%20cmake%20settting%20up%20Qt%20project/","excerpt":"","text":"Setting up Qt Projects with CMake and Linking External LibrariesIn the past few days, I attempted to develop a Windows application that utilized the Qt libraries for the GUI portion and libtorch and OpenCV for core calculations. The project was managed using cmake and compiled using the MSVC compiler. However, during development, I encountered several issues. In this article, I will detail these problems and provide solutions to fix them. What is cmake and Qt, libtorch… CMake is an open-source build system generator that is used to manage the build process of software projects. It is a cross-platform tool that generates build files for various operating systems, including Windows, Linux, and macOS. CMake allows developers to write build scripts in a simple and portable way, which can be used to build and manage software projects of different sizes and complexities. Qt is a cross-platform application development framework used for creating desktop, mobile, and embedded applications. One of the major advantages of Qt is its cross-platform support, allowing developers to create applications that can run on multiple operating systems such as Windows, Linux, macOS, and mobile platforms like Android and iOS. Qt also provides a rich set of GUI widgets and provides easy access to many platform-specific features. Libtorch is a C++ library for PyTorch, an open-source machine learning framework. Libtorch allows developers to use PyTorch in C++ applications, enabling high-performance machine learning inference on CPUs and GPUs. Libtorch provides a simple and flexible API for loading and running PyTorch models in C++.(In next session I will show you how to export PyTorch model and load it in C++ code) OpenCV (Open Source Computer Vision) is an open-source library of programming functions mainly aimed at real-time computer vision tasks, such as image processing, object detection, and machine learning. (In my project is just for load image) Environment needed C++17 or higher OpenCV 4.7.0 or higher LibTorch 1.9.0 or higher Qt 6.6.0 or higher MSVC compiler (from VS2022 or higher) :hammer: Export model from PyTorch and Load in C++ codeIn python train model code: # I use the res2next50 model as training model model = timm.create_model('res2next50', pretrained=False) # --------------- after trained --------------- # first use a input image = Image.open("/kaggle/input/lung-and-colon-cancer-histopathological-images/lung_colon_image_set/lung_image_sets/lung_n/lungn1007.jpeg") # make a prg image to tenser with size 1X3X224X224 image_tfm = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), ]) img = image_tfm(image) img = img[None, :] # do a forward and export model traced_script_module = torch.jit.trace(model, img) traced_script_module.save("resnet_model.pt") Load model in C++ and how to run it#include <torch/script.h> #include <opencv2/opencv.hpp> int main(){ // load module from enter path torch::jit::script::Module model; try { // Deserialize the ScriptModule from a file using torch::jit::load(). model = torch::jit::load(model_path); model.eval(); } catch (const c10::Error& e) { std::cerr << "error loading the model\\n"; std::cerr << e.what(); } // input image cv::Mat image; try { // Load image using OpenCV image = cv::imread(image_path); // Check if image was loaded successfully if (image.empty()) { throw std::runtime_error("Failed to load image"); } // Convert BGR to RGB format cv::cvtColor(image, image, cv::COLOR_BGR2RGB); // resize to 224*224 cv::resize(image, image, cv::Size(224, 224)); } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return -1; } // Create a vector of inputs. std::vector<torch::jit::IValue> inputs; // convert image to tenser auto tensor_image = torch::from_blob(image.data, { image.rows, image.cols, 3 }, torch::kByte) .permute({ 2, 0, 1 }); tensor_image = tensor_image.unsqueeze(0).to(torch::kFloat) / 255.0; // add tensor image to inputs inputs.push_back(tensor_image); // Execute the model and turn its output into a tensor. at::Tensor output = model.forward(inputs).toTensor(); // softmax to tenser that make sum of tensor item equal to 1 output = torch::softmax(output, 1); } In previous code has three3️⃣ points must notice: If you download the libtorch release version, you must build your project as a release version. If you build your project as a debug version, the program will not work. The running error will be as follows: error loading the model open file failed because of errno 42 on fopen: Illegal byte sequence, file path: Exception raised from RAIIFile at C:\\actions-runner\\_work\\pytorch\\pytorch\\builder\\windows\\pytorch\\caffe2\\serialize\\file_adapter.cc:27 (most recent call first): 00007FFB4C58F66200007FFB4C58F600 c10.dll!c10::Error::Error [<unknown file> @ <unknown line number>] 00007FFB4C58F2AA00007FFB4C58F250 c10.dll!c10::detail::torchCheckFail [<unknown file> @ <unknown line number>] 00007FFAFA62FF4300007FFAFA62FE50 torch_cpu.dll!caffe2::serialize::FileAdapter::FileAdapter [<unknown file> @ <unknown line number>] . [ INFO:[email protected]] global plugin_loader.impl.hpp:67 cv::plugin::impl::DynamicLib::libraryLoad load D:\\Qt_project\\build-LCHIC-Desktop_Qt_6_6_0_MSVC2019_64bit-Debug\\bin\\calc_core\\opencv_core_parallel_openmp470_64d.dll => FAILED [ INFO:[email protected]] global plugin_loader.impl.hpp:67 cv::plugin::impl::DynamicLib::libraryLoad load opencv_core_parallel_openmp470_64d.dll => FAILED Press <RETURN> to close this window... In the previous C++ code, on line 29, I converted the image format from BGR to RGB. By default, OpenCV loads images in BGR mode (blue, green, red), whereas the Image.open function in Python training code loads images in RGB format. In the previous C++ code, on line 54, I got the output for all probabilities. How can I get one specific probability and convert it to a C++ type (e.g., int, float, etc.)? The output size is 1xN. // get index 0 probability float pro = output[0][0].item<float>(); // get the max probability idx int max_pro_idx = torch::argmax(output).item<int>(); create shared library in windowsIn my project, I used the Qt libraries for the GUI portion and the libtorch and OpenCV libraries for the core calculations. Therefore, I want to create a shared library called model that links the PyTorch and OpenCV libraries together. # import libtorch set(CMAKE_PREFIX_PATH "replease to your libtorch installed folder📂") find_package(Torch REQUIRED) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}") # import opencv this need installed opencv and add environment path find_package(OpenCV REQUIRED) include_directories( ${OpenCV_INCLUDE_DIRS} ) # create shared libary model and link need lib add_library(model SHARED model.cpp) target_include_directories(model INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" ) set_target_properties(model PROPERTIES PUBLIC_HEADER include/model.h POSITION_INDEPENDENT_CODE 1 ) target_link_libraries(model PUBLIC "${TORCH_LIBRARIES}" "${OpenCV_LIBS}") # cpoy linked dll lib to build folder if (MSVC) add_custom_command(TARGET model POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:model> $<TARGET_FILE_DIR:model> COMMAND_EXPAND_LISTS ) file(GLOB TORCH_DLLS "${TORCH_INSTALL_PREFIX}/lib/*.dll") add_custom_command(TARGET model POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${TORCH_DLLS} $<TARGET_FILE_DIR:model>) endif (MSVC) # call install scrpit in build folder with # cmake --install ./ # install scrpit, install all lib install(TARGETS model LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) install(DIRECTORY $<TARGET_FILE_DIR:model>/ DESTINATION bin FILES_MATCHING PATTERN "*.dll" PATTERN "CMakeFiles" EXCLUDE ) But when I link to this shared library In GUI portion and build my project: target_link_libraries(myexe PRIVATE Qt${QT_VERSION_MAJOR}::Widgets model ) The error occurred, the cmake tell me cannot found model.lib. This error can be fixed by add this to you cmake project: # use make cmake in windows can create shared libary with msvc set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1) Qt portion issuesAfter successfully creating the shared library and including the header file, I encountered a new issue where the compiler reported many errors in the libtorch library header files. It was quite frustrating! After searching for similar issues, I discovered that the issue was caused by QT slots. To fix this issue, I needed to include the header file as follows: #undef slots #include <model.h> #define slots Q_SLOTS Package your application and make installerAfter finishing the project, the next step is to package it along with the necessary environment. My project uses Qt, libtorch, and OpenCV, and there are several ways to achieve this, my way is as follows: First, I copied the necessary environment libraries to the build folder📂 # cpoy linked dll lib to build folder if (MSVC) add_custom_command(TARGET model POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:model> $<TARGET_FILE_DIR:model> COMMAND_EXPAND_LISTS ) file(GLOB TORCH_DLLS "${TORCH_INSTALL_PREFIX}/lib/*.dll") add_custom_command(TARGET model POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${TORCH_DLLS} $<TARGET_FILE_DIR:model>) endif (MSVC) Second, I used a CMake install script to install them. # install scrpit, install all lib and model install(TARGETS model LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) install(DIRECTORY $<TARGET_FILE_DIR:model>/ DESTINATION bin FILES_MATCHING PATTERN "*.dll" PATTERN "CMakeFiles" EXCLUDE ) install(FILES "${PROJECT_SOURCE_DIR}/model/resnet_model.pt" DESTINATION bin/model ) install(DIRECTORY "${PROJECT_SOURCE_DIR}/dataset/" DESTINATION bin/dataset ) The Qt portion may be more difficult, but fortunately, Qt provides a CMake script that we can use to automatically call windeployqt.exe when running the CMake install script. # The command is defined in the Core component of the Qt6 package find_package(Qt6 REQUIRED COMPONENTS Core) qt_standard_project_setup() qt_generate_deploy_app_script( TARGET LCHIC OUTPUT_SCRIPT deploy_script NO_UNSUPPORTED_PLATFORM_ERROR ) install(SCRIPT ${deploy_script}) After completing the above steps, I ran the command prompt as an administrator and changed the directory to the project build folder. Then, I ran cmake --install ./ command. By default, the program and the necessary libraries will be installed in C:/Program Files (x86)/your_project_name. The next step is to package an installer, and Inno Setup could be a good choice. It provides a good GUI that makes it easy to create your application installer. If you want the user to not see the publisher as ‘Unknown’ when they install the application, you also need digital signing certificates and configure the signing tool. However, it is not free now. My project structureD:\\QT_PROJECT\\LCHIC | CMakeLists.txt | CMakeLists.txt.user | +---dataset | lungaca1007.jpeg | lungn1007.jpeg | lungscc1007.jpeg | +---model | resnet_model.pt | +---src | | CMakeLists.txt | | | +---calc_core | | | CMakeLists.txt | | | model.cpp | | | | | \\---include | | model.h | | | \\---gui | | CMakeLists.txt | | main.cpp | | mainwindow.cpp | | mainwindow.h | | mainwindow.ui | | | \\---res | app_win32.rc | favicon.ico | \\---test CMakeLists.txt test_core.cpp","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"Qt","slug":"Qt","permalink":"https://noahbishop.github.io/tags/Qt/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch12) Templates and Generic Programming (Not completed yet)","slug":"Cpp_c12","date":"2021-12-12T13:52:45.000Z","updated":"2023-04-21T15:59:18.010Z","comments":true,"path":"2021/12/12/Cpp_c12/","link":"","permalink":"https://noahbishop.github.io/2021/12/12/Cpp_c12/","excerpt":"","text":"Templates and Generic ProgrammingBoth object-oriented programming (OOP) and generic programming deal with types that are not known at the time the program is written. The distinction between the two is that OOP deals with types that are not known until run time, whereas in generic programming the types become known during compilation. For example, the library provides a single, generic definition of each container, such as vector. We can use that generic definition to define many different types of vectors, each of which differs from the others as to the type of elements the vector contains. Templates are the foundation for generic programming in C++. A template is a blueprint or formula for creating classes or functions. When we use a generic type, such as vector, or a generic function, such as find, we supply the information needed to transform that blueprint into a specific class or function. That transformation happens during compilation. Defining a TemplateImagine that we want to write a function to compare two values and indicate whether the first is less than, equal to, or greater than the second. In practice, we’d want to define several such functions, each of which will compare values of a given type. // compare string int compare(const string &v1, const string &v2) // compare double int compare(const double &v1, const double &v2) These functions are nearly identical: The only difference between them is the type of their parameters. The function body is the same in each function. Function TemplatesRather than defining a new function for each type, we can define a function template. A function template is a formula from which we can generate type-specific versions of that function. The template version of compare looks like: template <typename T> int compare(const T& l, const T& r) { if (r < l) { return 1; } if (l < r) { return -1; } return 0; } A template definition starts with the keyword template followed by a template parameter list, which is a comma-separated list of one or more template parameters bracketed by the < and > . The template parameter list acts much like a function parameter list. A function parameter list defines local variable(s) of a specified type but does not say how to initialize them. At run time, arguments are supplied that initialize the parameters. When we call a function template, the compiler (ordinarily) uses the arguments of the call to deduce the template argument(s) for us. That is, when we call compare, the compiler uses the type of the arguments to determine what type to bind to the template parameter T. For example, in this call std::cout << compare(4.5, 1.0) << std::endl; the arguments have type double. The compiler will deduce int as the template argument and will bind that argument to the template parameter T. Our compare function has one template type parameter. In general, we can use a type parameter as a type specifier in the same way that we use a built-in or class type specifier. In particular, a type parameter can be used to name the return type or a function parameter type, and for variable declarations or casts inside the function body: template <typename T> T foo(T* p) { T tmp = *p; // tmp will have the type to which p points // ... return tmp; } Each type parameter must be preceded by the keyword class or typename: // error: must precede U with either typename or class template <typename T, U> T calc(const T&, const U&); These keywords have the same meaning and can be used interchangeably inside a template parameter list. A template parameter list can use both keywords: // ok: no distinction between typename and class in a template parameter list template <typename T, class U> calc (const T&, const U&); It may seem more intuitive to use the keyword typename rather than class to designate a template type parameter. After all, we can use built-in (nonclass) types as a template type argument. Moreover, typename more clearly indicates that the name that follows is a type name. However, typename was added to C++ after templates were already in widespread use; some programmers continue to use class exclusively. In addition to defining type parameters, we can define templates that take nontype parameters. A nontype parameter represents a value rather than a type. Nontype parameters are specified by using a specific type name instead of the class or typename keyword. As an example, we can write a version of compare that will handle string literals. Such literals are arrays of const char. Because we cannot copy an array, we’ll define our parameters as references to an array. Because we’d like to be able to compare literals of different lengths, we’ll give our template two nontype parameters. The first template parameter will represent the size of the first array, and the second parameter will represent the size of the second array: template<unsigned N, unsigned M> int compare(const char(&p1)[N], const char(&p2)[M]) { // note need incloude string.h first return strcmp(p1, p2); } A function template can be declared inline or constexpr in the same ways as nontemplate functions. The inline or constexpr specifier follows the template parameter list and precedes the return type: // ok: inline specifier follows the template parameter list template <typename T> inline T min(const T&, const T&); // error: incorrect placement of the inline specifier inline template <typename T> T min(const T&, const T&); Simple though it is, our initial compare function illustrates two important principles for writing generic code: The function parameters in the template are references to const. The tests in the body use only < comparisons. However, by writing the code using only the < operator, we reduce the requirements on types that can be used with our compare function. Those types must support <, but they need not also support >. In fact, if we were truly concerned about type independence and portability, we probably should have defined our function using the less template <class T> int compare(const T& l, const T& r) { if (std::less<T>()(l, r)) { return -1; } if (std::less<T>()(r, l)) { return 1; } return 0; } The problem with our original version is that if a user calls it with two pointers and those pointers do not point to the same array, then our code is undefined. Class TemplatesA class template is a blueprint for generating classes. Class templates differ from function templates in that the compiler cannot deduce the template parameter type(s) for a class template. Instead, as we’ve seen many times, to use a class template we must supply additional information inside angle brackets following the template’s name. Like function templates, class templates begin with the keyword template followed by a template parameter list. In the definition of the class template (and its members), we use the template parameters as stand-ins for types or values that will be supplied when the template is used // in Blob.h #pragma once #include<vector> #include<memory> template <typename T> class Blob { public: typedef T value_type; typedef typename std::vector<T>::size_type size_type; // constructors Blob(); Blob(std::initializer_list<T> il); // number of elements in the Blob size_type size() const { return data->size(); } bool empty() const { return data->empty(); } // add and remove elements void push_back(const T& t) { data->push_back(t); } // move version; void push_back(T&& t) { data->push_back(std::move(t)); } void pop_back() { data->pop_back(); } // element access T& back() { return data->back(); } T& operator[](size_type i) { std::string msg = "error"; check(i, msg); return (*data)[i]; } private: std::shared_ptr<std::vector<T>> data; void check(size_type i, const std::string& msg) const; }; template<typename T> Blob<T>::Blob() :data(std::make_shared<std::vector<T>>()) { } template<typename T> Blob<T>::Blob(std::initializer_list<T> il) : data(std::make_shared<std::vector<T>>(il)) { } template<typename T> void Blob<T>::check(size_type i, const std::string& msg) const { if (i > data->size()) { throw std::out_of_range(msg); } } it’s worth to note in this program, I define the function in Blob.h file instead of bolb.cpp, you can try write template functions in bolb.cpp file see what happened. As we’ve seen many times, when we use a class template, we must supply extra information. We can now see that that extra information is a list of explicit template arguments that are bound to the template’s parameters. The compiler uses these template arguments to instantiate a specific class from the template. When the compiler instantiates a class from our Blob template, it rewrites the Blob template, replacing each instance of the template parameter T by the given template argument, which in this case is int. The compiler generates a different class for each element type we specify: // these definitions instantiate two distinct Blob types Blob<string> names; // Blob that holds strings Blob<double> prices;// different element type There is one exception to the rule that we must supply template arguments when we use a class template type. Inside the scope of the class template itself, we may use the name of the template without arguments: #pragma once #include"Blob.h" template <typename T> class BlobPtr { public: BlobPtr() : curr(0) {} BlobPtr(Blob<T>& a, size_t sz = 0) : wptr(a.data), curr(sz) { } T& operator*() const { auto p = check(curr, "dereference past end"); return (*p)[curr]; // (*p) is the vector to which this object points } // increment and decrement BlobPtr& operator++(); // prefix operators BlobPtr& operator--(); private: // check returns a shared_ptr to the vector if the check succeeds std::shared_ptr<std::vector<T>> check(std::size_t, const std::string&) const; // store a weak_ptr, which means the underlying vector might be destroyed std::weak_ptr<std::vector<T>> wptr; std::size_t curr; // current position within the array }; Notice: the prefix increment and decrement members of BlobPtr return BlobPtr&, not BlobPtr<T>&. When we are inside the scope of a class template, the compiler treats references to the template itself as if we had supplied template arguments matching the template’s own parameters. That is, it is as if we had written: BlobPtr<T>& operator++(); BlobPtr<T>& operator--(); When we define members outside the body of a class template, we must remember that we are not in the scope of the class until the class name is seen: template<typename T> inline BlobPtr<T>& BlobPtr<T>::operator++() { BlobPtr ret = *this; // save the current value ++* this; // advance one element; prefix ++ checks the increment return ret; // return the saved state } template<typename T> inline BlobPtr<T>& BlobPtr<T>::operator--() { BlobPtr ret = *this; // save the current value --* this; // advance one element; prefix ++ checks the increment return ret; // return the saved state } Because the return type appears outside the scope of the class, we must specify that the return type returns a BlobPtr instantiated with the same type as the class. Inside the function body, we are in the scope of the class so do not need to repeat the template argument when we define ret. When we do not supply template arguments, the compiler assumes that we are using the same type as the member’s instantiation. Hence, the definition of ret is as if we had written: BlobPtr<T> ret = *this; When a class contains a friend declaration, the class and the friend can independently be templates or not. A class template that has a nontemplate friend grants that friend access to all the instantiations of the template. When the friend is itself a template, the class granting friendship controls whether friendship includes all instantiations of the template or only specific instantiation(s).","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch11) Object-Oriented Programming","slug":"Cpp_c11","date":"2021-12-11T13:52:45.000Z","updated":"2023-04-21T15:57:13.015Z","comments":true,"path":"2021/12/11/Cpp_c11/","link":"","permalink":"https://noahbishop.github.io/2021/12/11/Cpp_c11/","excerpt":"","text":"Object-Oriented ProgrammingObject-oriented programming is based on three fundamental concepts: data abstraction, which we covered in Chapter 3, and inheritance and dynamic binding, which we’ll cover in this chapter. OOP: An OverviewThe key ideas in object-oriented programming are data abstraction, inheritance, and dynamic binding. Using data abstraction, we can define classes that separate interface from implementation (Chapter 3). Through inheritance, we can define classes that model the relationships among similar types. Through dynamic binding, we can use objects of these types while ignoring the details of how they differ. InheritanceClasses related by inheritance form a hierarchy. Typically there is a base class at the root of the hierarchy, from which the other classes inherit, directly or indirectly. These inheriting classes are known as derived classes. The base class defines those members that are common to the types in the hierarchy. Each derived class defines those members that are specific to the derived class itself. To model different kinds of pricing strategies, we’ll define a class named Quote, which will be the base class of our hierarchy. A Quote object will represent undiscounted books. From Quote we will inherit a second class, named Bulk_quote, to represent books that can be sold with a quantity discount. These classes will have the following two member functions: isbn(), which will return the ISBN. This operation does not depend on the specifics of the inherited class(es); it will be defined only in class Quote. net_price(size_t), which will return the price for purchasing a specified number of copies of a book. This operation is type specific; both Quote and Bulk_quote will define their own version of this function. In C++, a base class distinguishes functions that are type dependent from those that it expects its derived classes to inherit without change. The base class defines as virtual those functions it expects its derived classes to define for themselves. Using this knowledge, we can start to write our Quote class: class Quote { public: std::string isbn() const; virtual double net_price(std::size_t n) const; }; class Bulk_quote : public Quote { // Bulk_quote inherits from Quote public: double net_price(std::size_t) const override; }; A derived class must specify the class(es) from which it intends to inherit. It does so in a class derivation list, which is a colon followed by a comma-separated list of base classes each of which may have an optional access specifier. Because Bulk_quote uses public in its derivation list, we can use objects of type Bulk_quote as if they were Quote objects. A derived class must include in its own class body a declaration of all the virtual functions it intends to define for itself. A derived class may include the virtual keyword on these functions but is not required to do so. Dynamic BindingThrough dynamic binding, we can use the same code to process objects of either type Quote or Bulk_quote interchangeably. For example, the following function prints the total price for purchasing the given number of copies of a given book: // calculate and print the price for the given number of copies, applying any discounts double print_total(ostream& os, const Quote& item, size_t n) { // depending on the type of the object bound to the item parameter // calls either Quote::net_price or Bulk_quote::net_price double ret = item.net_price(n); os << "ISBN: " << item.isbn() // calls Quote::isbn << " # sold: " << n << " total due: " << ret << endl; return ret; } Defining Base and Derived ClassesIn many, but not all, ways base and derived classes are defined like other classes we have already seen. In this section, we’ll cover the basic features used to define classes related by inheritance. Defining a Base ClassWe’ll start by completing the definition of our Quote class: // in Quote.h #pragma once #include<string> class Quote { public: Quote() = default; Quote(std::string s, double p) :bookNo(s), price(p) {} virtual ~Quote() = default; std::string isbn(); virtual double net_price(std::size_t n); private: std::string bookNo; protected: double price = 0.0; }; // in Quote.cpp #include "Quote.h" std::string Quote::isbn() { return bookNo; } double Quote::net_price(std::size_t n) { return n * price; } Derived classes inherit the members of their base class. However, a derived class needs to be able to provide its own definition for operations, such as net_price, that are type dependent. In such cases, the derived class needs to override the definition it inherits from the base class, by providing its own definition. In C++, a base class must distinguish the functions it expects its derived classes to override from those that it expects its derived classes to inherit without change. The base class defines as virtual those functions it expects its derived classes to override. Member functions that are not declared as virtual are resolved at compile time, not run time. For the isbn member, this is exactly the behavior we want. The isbn function does not depend on the details of a derived type. A derived class inherits the members defined in its base class. However, the member functions in a derived class may not necessarily access the members that are inherited from the base class. Like any other code that uses the base class, a derived class may access the public members of its base class but may not access the private members. However, sometimes a base class has members that it wants to let itsderived classes use while still prohibiting access to those same members by other users. We specify such members after a protected access specifier. Defining a Derived ClassA derived class must specify from which class(es) it inherits. It does so in its class derivation list, which is a colon followed by a comma-separated list of names of previously defined classes. Each base class name may be preceded by an optional access specifier, which is one of public, protected, or private. When the derivation is public, the public members of the base class become part of the interface of the derived class as well. In addition, we can bind an object of a publicly derived type to a pointer or reference to the base type. Because we used public in the derivation list, the interface to Bulk_quote implicitly contains the isbn function, and we may use a Bulk_quote object where a pointer or reference to Quote is expected. #pragma once #include<string> class Quote { public: Quote() = default; // isbn, price , minimum quantity, discount Quote(std::string s, double p) :bookNo(s), price(p) {} virtual ~Quote() = default; std::string isbn(); virtual double net_price(std::size_t n) const; private: std::string bookNo; protected: double price = 0.0; }; Our Bulk_quote class inherits the isbn function and the bookNo and price data members of its Quote base class. It defines its own version of net_price and has two additional data members, min_qty and discount. These members specify the minimum quantity and the discount to apply once that number of copies are purchased. Derived classes frequently, but not always, override the virtual functions that they inherit. If a derived class does not override a virtual from its base, then, like any other member, the derived class inherits the version defined in its base class. Although a derived object contains members that it inherits from its base, it cannot directly initialize those members. Like any other code that creates an object of the base-class type, a derived class must use a base-class constructor to initialize its base-class part. #include "Bulk_quote.h" Bulk_quote::Bulk_quote(const std::string& is, double pr, std::size_t min_n, double dis) :Quote(is, pr), min_qty(min_n), discount(dis) { } double Bulk_quote::net_price(std::size_t n) const { if (n >= min_qty) { return (1 - discount) * price * n; } return price * n; } Some detailsA derived class is declared like any other class. The declaration contains the class name but does not include its derivation list: class Bulk_quote : public Quote; // error: derivation list can't appear here class Bulk_quote; // ok: right way to declare a derived class A class must be defined, not just declared, before we can use it as a base class: class Quote; // declared but not defined // error: Quote must be defined class Bulk_quote : public Quote { ... }; Sometimes we define a class that we don’t want others to inherit from. Or we might define a class for which we don’t want to think about whether it is appropriate as a base class. Under the new standard, we can prevent a class from being used as a base by following the class name with final: class NoDerived final { /* */ }; // NoDerived can't be a base class Conversions and InheritanceOrdinarily, we can bind a reference or a pointer only to an object that has the same type as the corresponding reference or pointer or to a type that involves an acceptable const conversion. Classesrelated by inheritance are an important exception: We can bind a pointer or reference to a base-class type to an object of a type derived from that base class. For example, we can use a Quote& to refer to a Bulk_quote object, and we can assign the address of a Bulk_quote object to a Quote*. When we use types related by inheritance, we often need to distinguish between the static type of a variable or other expression and the dynamic type of the object that expression represents. The static type of an expression is always known at compile time—it is the type with which a variable is declared or that an expression yields. The dynamic type is the type of the object in memory that the variable or expression represents. The dynamic type may not be known until run time. The conversion from derived to base exists because every derived object contains a base-class part to which a pointer or reference of the base-class type can be bound. There is no similar guarantee for base-class objects. Virtual FunctionsWhen a virtual function is called through a reference or pointer, the compiler generates code to decide at run time which function to call. The function that is called is the one that corresponds to the dynamic type of the object bound to that pointer or reference. As an example, you can run the next code in your computer: #include"Bulk_quote.h" #include<iostream> int main() { std::string s = "54742675"; Bulk_quote b(s, 10, 8, 0.2); // by refreance, out put is 80 Quote& p1 = b; std::cout << p1.net_price(10) << std::endl; // by pointer, out put is 80 Quote* p2 = &b; std::cout << p2->net_price(10) << std::endl; // either, out put is 100 Quote p3 = b; std::cout << p3.net_price(10) << std::endl; return 0; } dynamic binding happens only when a virtual function is called through a pointer or a reference. When we call a virtual function on an expression that has a plain—nonreference and nonpointer—type, that call is bound at compile time. When a derived class overrides a virtual function, it may, but is not required to, repeat the virtual keyword. Once a function is declared as virtual, it remains virtual in all the derived classes. A derived-class function that overrides an inherited virtual function must have exactly the same parameter type and return type as the base-class function that it overrides. it is legal for a derived class to define a function with the same name as a virtual in its base class but with a different parameter list. The compiler considers such a function to be independent from the base-class function. In such cases, the derived version does not override the version in the base class. In practice, such declarations often are a mistake—the class author intended to override a virtual from the base class but made a mistake in specifying the parameter list. Under the new standard we can specify override on a virtual function in a derived class. Doing so makes our intention clear and (more importantly) enlists the compiler in finding such problems for us. The compiler will reject a program if a function marked override does not override an existing virtual function: struct B { virtual void f1(int) const; virtual void f2(); void f3(); }; struct D1 : B { void f1(int) const override; // ok: f1 matches f1 in the base void f2(int) override; // error: B has no f2(int) function void f3() override; // error: f3 not virtual void f4() override; // error: B doesn't have a function named f4 }; We can also designate a function as final. Any attempt to override a function that has been defined as final will be flagged as an error: struct D2 : B { // inherits f2() and f3() from B and overrides f1(int) void f1(int) const final; // subsequent classes can't override f1(int) }; struct D3 : D2 { void f2(); // ok: overrides f2 inherited from the indirect base,B void f1(int) const; // error: D2 declared f2 as final }; Some detailLike any other function, a virtual function can have default arguments. If a call uses a default argument, the value that is used is the one defined by the static type through which the function is called. That is, when a call is made through a reference or pointer to base, the default argument(s) will be those defined in the base class. The base-class arguments will be used even when the derived version of the function is run. In this case, the derived function will be passed the default arguments defined for the base-class version of the function. If the derived function relies on being passed different arguments, theprogram will not execute as expected. In some cases, we want to prevent dynamic binding of a call to a virtual function; we want to force the call to use a particular version of that virtual. We can use the scope operator to do so. For example, this code: Quote* p2 = &b; std::cout << p2->Quote::net_price(10) << std::endl; Abstract Base ClassesImagine that we want to extend our bookstore classes to support several discount strategies. In addition to a bulk discount, we might offer a discount for purchases up to a certain quantity and then charge the full price thereafter. Or we might offer a discount for purchases above a certain limit but not for purchases up to that limit. Each of these discount strategies is the same in that it requires a quantity and a discount amount. We might support these differing strategies by defining a new class named Disc_quote to store the quantity and the discount amount. Classes, such as Bulk_item, that represent a specific discount strategy will inherit from Disc_quote. Each of the derived classes will implement its discount strategy by defining its own version of net_price. Before we can define our Disc_Quote class, we have to decide what to do about net_price. Our Disc_quote class doesn’t correspond to any particular discount strategy; there is no meaning to ascribe to net_price for this class. We could define Disc_quote without its own version of net_price. In this case, Disc_quote would inherit net_price from Quote. However, this design would make it possible for our users to write nonsensical code. A user could create an object of type Disc_quote by supplying a quantity and a discount rate. Passing that Disc_quote object to a function such as print_total would use the Quote version of net_price. The calculated price would not include the discount that was supplied when the object was created. That state of affairsmakes no sense. Pure Virtual FunctionsThinking about the question in this detail reveals that our problem is not just that we don’t know how to define net_price. In practice, we’d like to prevent users from creating Disc_quote objects at all. We can enforce this design intent—and make it clear that there is no meaning for net_price—by defining net_price as a pure virtual function. Unlike ordinary virtuals, a pure virtual function does not have to be defined. We specify that a virtual function is a pure virtual by writing = 0 in place of a function body. #pragma once #include "Quote.h" class Disc_Quote : public Quote { public: Disc_Quote() = default; Disc_Quote(const std::string& book, double price, std::size_t qty, double disc) : Quote(book, price), quantity(qty), discount(disc) { } double net_price(std::size_t) const = 0; protected: std::size_t quantity = 0; // purchase size for the discount to apply double discount = 0.0; // fractional discount to apply }; A class containing (or inheriting without overriding) a pure virtual function is an abstract base class. An abstract base class defines an interface for subsequent classes to override. We cannot (directly) create objects of a type that is an abstract base class. Because Disc_quote defines net_price as a pure virtual, we cannot define objects of type Disc_quote. We can define objects of classes that inherit from Disc_quote, so long as those classes override net_price: Disc_Quote discounted; // error: can't define a Disc_quote object A Derived Class Constructor Initializes Its Direct Base Class OnlyNow we can reimplement Bulk_quote to inherit from Disc_quote rather than inheriting directly from Quote: #pragma once #include "Disc_Quote.h" class Bulk_quote : public Disc_Quote { public: Bulk_quote() = default; // isbn, price , minimum quantity, discount Bulk_quote(const std::string&, double, std::size_t, double); double net_price(std::size_t n) const override; }; #include "Bulk_quote.h" // isbn, price , minimum quantity, discount Bulk_quote::Bulk_quote(const std::string& is, double pr, std::size_t min_n, double dis) :Disc_Quote(is, pr, min_n, dis) { } double Bulk_quote::net_price(std::size_t n) const { if (n >= quantity) { return (1 - discount) * price * n; } return price * n; } Access Control and InheritanceJust as each class controls the initialization of its own members, each class also controls whether its members are accessible to a derived class. protected MembersAs we’ve seen, a class uses protected for those members that it is willing to share with its derived classes but wants to protect from general access. The protected specifier can be thought of as a blend of private and public: Like private, protected members are inaccessible to users of the class. Like public, protected members are accessible to members and friends of classes derived from this class. In addition, protected has another important property: A derived class member or friend may access the protected members of the base class only through a derived object. The derived class has no special access to the protected members of base-class objects. public, private, and protected InheritanceAccess to a member that a class inherits is controlled by a combination of the access specifier for that member in the base class, and the access specifier in the derivation list of the derived class. As an example, consider the following hierarchy: class Base { public: void pub_mem(); // public member protected: int prot_mem; // protected member private: char priv_mem; // private member }; struct Pub_Derv : public Base { // ok: derived classes can access protected members int f() { return prot_mem; } // error: private members are inaccessible to derived classes char g() { return priv_mem; } }; struct Priv_Derv : private Base { // private derivation doesn't affect access in the derived class int f1() const { return prot_mem; } }; The derivation access specifier has no effect on whether members (and friends) of a derived class may access the members of its own direct base class. Access to the members of a base class is controlled by the access specifiers in the base class itself. Both Pub_Derv and Priv_Derv may access the protected member prot_mem. Neither may access the private member priv_mem. The purpose of the derivation access specifier is to control the access that users of the derived class—including other classes derived from the derived class—have to the members inherited from Base: Pub_Derv d1; // members inherited from Base are public Priv_Derv d2; // members inherited from Base are private d1.pub_mem(); // ok: pub_mem is public in the derived class d2.pub_mem(); // error: pub_mem is private in the derived class The derivation access specifier used by a derived class also controls access from classes that inherit from that derived class: struct Derived_from_Public : public Pub_Derv { // ok: Base::prot_mem remains protected in Pub_Derv int use_base() { return prot_mem; } }; struct Derived_from_Private : public Priv_Derv { // error: Base::prot_mem is private in Priv_Derv int use_base() { return prot_mem; } }; Classes derived from Pub_Derv may access prot_mem from Base because that member remains a protected member in Pub_Derv. In contrast, classes derived from Priv_Derv have no such access. To them, all the members that Priv_Derv inherited from Base are private. Friendship and InheritanceJust as friendship is not transitive, friendship is also not inherited. Friends of the base have no special access to members of its derived classes, and friends of a derived class have no special access to the base class: class Base { // added friend declaration; other members as before friend class Pal; // Pal has no access to classes derived from Base }; class Pal { public: int f(Base b) { return b.prot_mem; } // ok: Pal is a friend of Base int f2(Sneaky s) { return s.j; } // error: Pal not friend of Sneaky // access to a base class is controlled by the base class, even inside a derived object int f3(Sneaky s) { return s.prot_mem; } // ok: Pal is a friend }; Exempting Individual MembersSometimes we need to change the access level of a name that a derived class inherits. We can do so by providing a using declaration: class Base { public: std::size_t size() const { return n; } protected: std::size_t n; }; class Derived : private Base { // note: private inheritance public: // maintain access levels for members related to the size of the object using Base::size; protected: using Base::n; }; Because Derived uses private inheritance, the inherited members, size and n, are (by default) private members of Derived. The using declarations adjust the accessibility of these members. Users of Derived can access the size member, and classes subsequently derived from Derived can access n. In previous we saw that classes defined with the struct and class keywords have different default access specifiers. Similarly, the default derivation specifier depends on which keyword is used to define a derived class. By default, a derived class defined with the class keyword has private inheritance; a derived class defined with struct has public inheritance Class Scope under InheritanceEach class defines its own scope within which its members are defined. Under inheritance, the scope of a derived class is nested inside the scope of its base classes. If a name is unresolved within the scope of the derived class, the enclosing base-class scopes are searched for a definition of that name. Name Lookup Happens at Compile TimeThe static type of an object, reference, or pointer determines which members of that object are visible. Even when the static and dynamic types might differ (as can happen when a reference or pointer to a base class is used), the static type determines what members can be used. As an example, we might add a member to the Disc_quote class that returns a pair holding the minimum (or maximum) quantity and the discounted price: class Disc_quote : public Quote { public: std::pair<size_t, double> discount_policy() const { return {quantity, discount}; } // other members as before }; We can use discount_policy only through an object, pointer, or reference of type Disc_quote or of a class derived from Disc_quote: Bulk_quote bulk; Bulk_quote *bulkP = &bulk; // static and dynamic types are the same Quote *itemP = &bulk; // static and dynamic types differ bulkP->discount_policy(); // ok: bulkP has type Bulk_quote* itemP->discount_policy(); // error: itemP has type Quote* Even though bulk has a member named discount_policy, that member is not visible through itemP. The type of itemP is a pointer to Quote, which means that the search for discount_policy starts in class Quote. The Quote class has no member named discount_policy, so we cannot call that member on an object, reference, or pointer of type Quote. Name Collisions and InheritanceLike any other scope, a derived class can reuse a name defined in one of its direct or indirect base classes. As usual, names defined in an inner scope hide uses of that name in the outer scope: struct Base { Base() : mem(0) {} public: int mem; }; struct Derived : Base { Derived(int i) : mem(i) {} // initializes Derived::mem to i // Base::mem is default initialized int get_mem() { return mem; } // returns Derived::mem public: int mem; // hides mem in the base }; We use the :: select scope: Derived p(19); std::cout << p.mem << std::endl; // output is 19 std::cout << p.Base::mem << std::endl;// output is 0 As we’ve seen, functions declared in an inner scope do not overload functions declared in an outer scope. As a result, functions defined in a derived class do not overload members defined in its base class(es). As in any other scope, if a member in a derived class (i.e., in an inner scope) has the same name as a base class member (i.e., a name defined in an outer scope), then the derived member hides the base-class member within the scope of the derived class. The base member is hidden even if the functions have different parameter lists: struct Base { int memfcn(); }; struct Derived : Base { int memfcn(int); // hides memfcn in the base }; Derived d; Base b; b.memfcn(); // calls Base::memfcn d.memfcn(10); // calls Derived::memfcn d.memfcn(); // error: memfcn with no arguments is hidden d.Base::memfcn(); // ok: calls Base::memfcn Virtual Functions and ScopeWe can now understand why virtual functions must have the same parameter list in the base and derived classes. If the base and derived members took arguments that differed from one another, there would be no way to call the derived version through a reference or pointer to the base class. For example: class Base { public: virtual int fcn(); }; class D1 : public Base { public: // hides fcn in the base; this fcn is not virtual // D1 inherits the definition of Base::fcn() int fcn(int); // parameter list differs from fcn in Base virtual void f2(); // new virtual function that does not exist in Base }; class D2 : public D1 { public: int fcn(int); // nonvirtual function hides D1::fcn(int) int fcn(); // overrides virtual fcn from Base void f2(); // overrides virtual f2 from D1 }; Constructors and Copy ControlLike any other class, a class in an inheritance hierarchy controls what happens when objects of its type are created, copied, moved, assigned, or destroyed. As for any other class, if a class (base or derived) does not itself define one of the copy-control operations, the compiler will synthesize that operation. Also, as usual, the synthesized version of any of these members might be a deleted function. Virtual DestructorsThe primary direct impact that inheritance has on copy control for a base class is that a base class generally should define a virtual destructor. The destructor needs to be virtual to allow objects in the inheritance hierarchy to be dynamically allocated. Recall that the destructor is run when we delete a pointer to a dynamically allocated object. If that pointer points to a type in an inheritance hierarchy, it is possible that the static type of the pointer might differ from the dynamic type of the object being destroyed. For example, if we delete a pointer of type Quote*, that pointer might point at a Bulk_quote object. If the pointer points at a Bulk_quote, the compiler has to know that it should run the Bulk_quote destructor. As with any other function, we arrange to run the proper destructor by defining the destructor as virtual in the base class. Like any other virtual, the virtual nature of the destructor is inherited. Thus, classes derived from Quote have virtual destructors, whether they use the synthesized destructor or define their own version. So long as the base class destructor is virtual, when we delete a pointer to base, the correct destructor will be run: Quote *itemP = new Quote; // same static and dynamic type delete itemP; // destructor for Quote called itemP = new Bulk_quote; // static and dynamic types differ delete itemP; // destructor for Bulk_quote called Synthesized Copy Control and InheritanceThe synthesized copy-control members in a base or a derived class execute like any other synthesized constructor, assignment operator, or destructor: They memberwise initialize, assign, or destroy the members of the class itself. In addition, these synthesized members initialize, assign, or destroy the direct base part of an object by using the corresponding operation from the base class. the synthesized Bulk_quote copy constructor uses the (synthesized) Disc_quote copy constructor, which uses the (synthesized) Quote copy constructor. The Quote copy constructor copies the bookNo and price members; and the Disc_Quote copy constructor copies the qty and discount members. The synthesized default constructor, or any of the copy-control members of either a base or a derived class, may be defined as deleted for the same reasons as in any other class: If the default constructor, copy constructor, copy-assignment operator, or destructor in the base class is deleted or inaccessible, then the corresponding member in the derived class is defined as deleted, because the compiler can’t use the base-class member to construct, assign, or destroy the base-class part of the object. If the base class has an inaccessible or deleted destructor, then the synthesized default and copy constructors in the derived classes are defined as deleted, because there is no way to destroy the base part of the derived object. As usual, the compiler will not synthesize a deleted move operation. If we use =default to request a move operation, it will be a deleted function in the derived if the corresponding operation in the base is deleted or inaccessible,because the base class part cannot be moved. The move constructor will also be deleted if the base class destructor is deleted or inaccessible. class B { public: B(); B(const B&) = delete; // other members, not including a move constructor }; class D : public B { // no constructors }; D d; // ok: D's synthesized default constructor uses B's default constructor D d2(d); // error: D's synthesized copy constructor is deleted D d3(std::move(d)); // error: implicitly uses D's deleted copy constructor Derived-Class Copy-Control MembersWhen we define a copy or move constructor for a derived class, we ordinarily use the corresponding base-class constructor to initialize the base part of the object: class Base { /* ... */ }; class D : public Base { public: // by default, the base class default constructor initializes the base part of an object // to use the copy or move constructor, we must explicitly call that // constructor in the constructor initializer list D(const D& d) : Base(d) // copy the base members /* initializers for members of D */ { /* ... */ } D(D&& d) : Base(std::move(d)) // move the base members /* initializers for members of D */ { /* ... */ } }; Like the copy and move constructors, a derived-class assignment operator, must assign its base part explicitly: // Base::operator=(const Base&) is not invoked automatically D& D::operator=(const D& rhs) { Base::operator=(rhs); // assigns the base part // assign the members in the derived class, as usual, // handling self-assignment and freeing existing resources as appropriate return *this; } Recall that the data members of an object are implicitly destroyed after the destructor body completes. Similarly, the base-class parts of an object are also implicitly destroyed. As a result, unlike the constructors and assignment operators, a derived destructor is responsible only for destroying the resources allocated by the derived class: class D: public Base { public: // Base::~Base invoked automatically ~D() { /* do what it takes to clean up derived members */ } }; Objects are destroyed in the opposite order from which they are constructed: The derived destructor is run first, and then the base-class destructors are invoked, back up through the inheritance hierarchy. Inherited ConstructorsUnder the new standard, a derived class can reuse the constructors defined by its direct base class. Although, as we’ll see, such constructors are not inherited in the normal sense of that term, it is nonetheless common to refer to such constructors as “inherited.” For the same reasons that a class may initialize only its direct base class, a class may inherit constructors only from its direct base. A class cannot inherit the default, copy, and move constructors. If the derived class does not directly define these constructors, the compiler synthesizes them as usual. A derived class inherits its base-class constructors by providing a using declaration that names its (direct) base class. As an example, we can redefine our Bulk_quote class to inherit its constructors from Disc_quote: class Bulk_quote : public Disc_quote { public: using Disc_quote::Disc_quote; // inherit Disc_quote's constructors double net_price(std::size_t) const; }; Containers and InheritanceWhen we use a container to store objects from an inheritance hierarchy, we generally must store those objects indirectly. We cannot put objects of types related by inheritance directly into a container, because there is no way to define a container that holds elements of differing types. As an example, assume we want to define a vector to hold several books that a customer wants to buy. It should be easy to see that we can’t use a vector that holds Bulk_quote objects. We can’t convert Quote objects to Bulk_quote, so we wouldn’t be able to put Quote objects into that vector. It may be somewhat less obvious that we also can’t use a vector that holds objects of type Quote. In this case, we can put Bulk_quote objects into the container. However, those objects would no longer be Bulk_quote objects: vector<Quote> basket; basket.push_back(Quote("0-201-82470-1", 50)); // ok, but copies only the Quote part of the object into basket basket.push_back(Bulk_quote("0-201-54848-8", 50, 10, .25)); // calls version defined by Quote, prints 750, i.e., 15 * $50 cout << basket.back().net_price(15) << endl; The elements in basket are Quote objects. When we add a Bulk_quote object to the vector its derived part is ignored. When we need a container that holds objects related by inheritance, we typically define the container to hold pointers (preferably smart pointers) to the base class. As usual, the dynamic type of the object to which those pointers point might be the base-class type or a type derived from that base: vector<shared_ptr<Quote>> basket; basket.push_back(make_shared<Quote>("0-201-82470-1", 50)); basket.push_back( make_shared<Bulk_quote>("0-201-54848-8", 50, 10, .25)); // calls the version defined by Quote; prints 562.5, i.e., 15 * $50 less the discount cout << basket.back()->net_price(15) << endl; Writing a Basket ClassOne of the ironies of object-oriented programming in C++ is that we cannot use objects directly to support it. Instead, we must use pointers and references. Because pointers impose complexity on our programs, we often define auxiliary classes to help manage that complexity. We’ll start by defining a class to represent a basket: // Basket.h #pragma once #include<memory> #include"Quote.h" #include<set> class Basket { public: // Basket uses synthesized default constructor and copy-control members void add_item(const std::shared_ptr<Quote>& sale) { items.insert(sale); } // prints the total price for each book and the overall total for all items in the basket double total_receipt(std::ostream&) const; private: static bool compare(const std::shared_ptr<Quote>& lhs, const std::shared_ptr<Quote>& rhs) { return lhs->isbn() < rhs->isbn(); } std::multiset<std::shared_ptr<Quote>, decltype(compare)*> items{ compare }; }; The elements in our multiset are shared_ptrs and there is no less-than operator for shared_ptr. As a result, we must provide our own comparison operation to order the elements. Here, we define a privatestatic member, named compare, that compares the isbns of the objects to which the shared_ptrs point. We initialize our multiset to use this comparison function through an in-class initializer. The Basket class defines only two operations. We defined the add_item member inside the class. That member takes a shared_ptr to a dynamically allocated Quote and puts that shared_ptr into the multiset. The second member, total_receipt, prints an itemized bill for the contents of the basket and returns the price for all the items in the basket: // Basket.cpp #include "Basket.h" double print_total(std::ostream& os, const Quote& item, size_t n) { // depending on the type of the object bound to the item parameter // calls either Quote::net_price or Bulk_quote::net_price double ret = item.net_price(n); os << "ISBN: " << item.isbn() // calls Quote::isbn << " # sold: " << n << " total due: " << ret << std::endl; return ret; } double Basket::total_receipt(std::ostream& os) const { double sum = 0.0; // holds the running total // iter refers to the first element in a batch of elements with the same ISBN // upper_bound returns an iterator to the element just past the end of that batch for (auto iter = items.cbegin(); iter != items.cend(); iter = items.upper_bound(*iter)) { // we know there's at least one element with this key in the Basket // print the line item for this book sum += print_total(os, **iter, items.count(*iter)); } os << "Total Sale: " << sum << std::endl; // print the final overall total return sum; } The interesting bit is the “increment” expression in the for. Rather than the usual loop that reads each element, we advance iter to refer to the next key. We skip over all the elements that match the current key by calling upper_bound. The call to upper_bound returns the iterator that refers to the element just past the last one with the same key as in iter. The iterator we get back denotes either the end of the set or the next book. Users of Basket still have to deal with dynamic memory, because add_item takes a shared_ptr. As a result, users have to write code such as Basket b; b.add_item(std::make_shared<Quote>("111", 50)); b.add_item(std::make_shared<Quote>("124", 60)); Our next step will be to redefine add_item so that it takes a Quote object instead of a shared_ptr. This new version of add_item will handle the memory allocation so that our users no longer need to do so. We’ll define two versions, one that will copy its given object and the other that will move from it: void add_item(const Quote& sale); // copy the given object void add_item(Quote&& sale); // move the given object The only problem is that add_item doesn’t know what type to allocate. When it does its memory allocation, add_item will copy (or move) its sale parameter. We’ll solve this problem by giving our Quote classes a virtual member that allocates a copy of itself. class Quote { public: // virtual function to return a dynamically allocated copy of itself // these members use reference qualifiers; see §13.6.3 (p. 546) virtual Quote* clone() const& { return new Quote(*this); } virtual Quote* clone()&& { return new Quote(std::move(*this)); } // other members as before }; class Bulk_quote : public Quote { Bulk_quote* clone() const& { return new Bulk_quote(*this); } Bulk_quote* clone()&& { return new Bulk_quote(std::move(*this)); } // other members as before }; Using clone, it is easy to write our new versions of add_item: class Basket { public: void add_item(const Quote& sale) // copy the given object { items.insert(std::shared_ptr<Quote>(sale.clone())); } void add_item(Quote&& sale) // move the given object { items.insert( std::shared_ptr<Quote>(std::move(sale).clone())); } // other members as before };","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch10) Overloaded Operations and Conversions","slug":"Cpp_c10","date":"2021-12-10T13:52:45.000Z","updated":"2023-04-21T15:56:24.348Z","comments":true,"path":"2021/12/10/Cpp_c10/","link":"","permalink":"https://noahbishop.github.io/2021/12/10/Cpp_c10/","excerpt":"","text":"Overloaded Operations and ConversionsIn Chapter 2, we saw that C++ defines a large number of operators and automatic conversions among the built-in types. These facilities allow programmers to write a rich set of mixed-type expressions. C++ lets us define what the operators mean when applied to objects of class type. It also lets us define conversions for class types. Class-type conversions are used like the built-in conversions to implicitly convert an object of one type to another type when needed. Basic ConceptsOverloaded operators are functions with special names: the keyword operator followed by the symbol for the operator being defined. Like any other function, an overloaded operator has a return type, a parameter list, and a body. An overloaded operator function has the same number of parameters as the operator has operands. A unary operator has one parameter; a binary operator has two. In a binary operator, the left-hand operand is passed to the first parameter and the right-hand operand to the second. Except for the overloaded function-call operator, operator(), an overloaded operator may not have default arguments. If an operator function is a member function, the first (left-hand) operand is bound to the implicit this pointer. Because the first operand is implicitly bound to this, a member operator function has one less (explicit) parameter than the operator has operands. We can overload only existing operators and cannot invent new operator symbols. For example, we cannot define operator** to provide exponentiation. Calling an Overloaded Operator Function DirectlyOrdinarily, we “call” an overloaded operator function indirectly by using the operator on arguments of the appropriate type. However, we can also call an overloaded operator function directly in the same way that we call an ordinary function. We name the function and pass an appropriate number of arguments of the appropriate type, We call a member operator function explicitly in the same way that we call any other member function. // equivalent calls to a nonmember operator function data1 + data2; // normal expression operator+(data1, data2); // equivalent function call data1 += data2; // expression-based ''call'' data1.operator+=(data2); // equivalent call to a member operator function Some Operators Shouldn’t Be OverloadedRecall that a few operators guarantee the order in which operands are evaluated. Because using an overloaded operator is really a function call, these guarantees do not apply to overloaded operators. In particular, the operand-evaluation guarantees of the logical AND, logical OR, and comma operators arenot preserved. Use Definitions That Are Consistent with the Built-in MeaningWhen you design a class, you should always think first about what operations the class will provide. Only after you know what operations are needed should you think about whether to define each operation as an ordinary function or as an overloaded operator. Those operations with a logical mapping to an operator are good candidates for defining as overloaded operators: If the class does IO, define the shift operators to be consistent with how IO is done for the built-in types. If the class has an operation to test for equality, define operator==. If the class has operator==, it should usually have operator!= as well. If the class has a single, natural ordering operation, define operator<. If the class has operator<, it should probably have all of the relational operators. The return type of an overloaded operator usually should be compatible with the return from the built-in version of the operator: The logical and relational operators should return bool, the arithmetic operators should return a value of the class type, and assignment and compound assignment should return a reference to the left-hand operand. Choosing Member or Nonmember ImplementationWhen we define an overloaded operator, we must decide whether to make the operator a class member or an ordinary nonmember function. In some cases, there is no choice—some operators are required to be members; in other cases, we may not be able to define the operator appropriately if it is a member. The following guidelines can be of help in deciding whether to make an operator a member or an ordinary nonmember function: The assignment (=), subscript ([]), call (()), and member access arrow (->) operators must be defined as members. The compound-assignment operators ordinarily ought to be members. However, unlike assignment, they are not required to be members. Operators that change the state of their object or that are closely tied to theirgiven type—such as increment, decrement, and dereference—usually should bemembers. Symmetric operators—those that might convert either operand, such as the arithmetic, equality, relational, and bitwise operators—usually should be defined as ordinary nonmember functions. Input and Output OperatorsAs we’ve seen, the IO library uses >> and << for input and output, respectively. The IO library itself defines versions of these operators to read and write the built-in types. Classes that support IO ordinarily define versions of these operators for objects of the class type. Overloading the Output Operator <<Ordinarily, the first parameter of an output operator is a reference to a nonconst ostream object. The second parameter ordinarily should be a reference to const of the class type we want to print. To be consistent with other output operators, operator<< normally returns its ostream parameter. As an example, we’ll write the Sales_data output operator:(Sales_data in here) { // in class scope friend std::ostream& operator<<(std::ostream&, const Sales_data&); } std::ostream& operator<<(std::ostream& os, const Sales_data& item) { os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price(); return os; } Input and output operators that conform to the conventions of the iostream library must be ordinary nonmember functions. These operators cannot be members of our own class. IO Operators Must Be Nonmember Functions. Overloading the Input Operator >>Ordinarily the first parameter of an input operator is a reference to the stream from which it is to read, and the second parameter is a reference to the (nonconst) object into which to read. The operator usually returns a reference to its given stream. The second parameter must be nonconst because the purpose of an input operator is to read data into this object. As an example, we’ll write the Sales_data input operator: { // in class scope friend std::istream& operator>>(std::istream&, const Sales_data&); } inline std::istream& operator>>(std::istream& is, Sales_data& item) { double price = 0; is >> item.bookNo >> item.units_sold >> price; if (is) { item.revenue = item.units_sold * price; } else { item = Sales_data(); } return is; } Except for the if statement, this definition is similar to our earlier read function. The if checks whether the reads were successful. If an IO error occurs, the operator resets its given object to the empty Sales_data. That way, the object is guaranteed to be in a consistent state. Arithmetic and Relational OperatorsOrdinarily, we define the arithmetic and relational operators as nonmember functions in order to allow conversions for either the left- or right-hand operand. These operators shouldn’t need to change the state of either operand, so the parameters are ordinarily references to const. An arithmetic operator usually generates a new value that is the result of a computation on its two operands. That value is distinct from either operand and is calculated in a local variable. The operation returns a copy of this local as its result. Classes that define an arithmetic operator generally define the corresponding compound assignment operator as well. When a class has both operators, it is usually more efficient to define the arithmetic operator to use compound assignment: // assumes that both objects refer to the same book Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) { Sales_data sum = lhs; // copy data members from lhs into sum sum += rhs; // add rhs into sum return sum; } Equality OperatorsOrdinarily, classes in C++ define the equality operator to test whether two objects are equivalent. That is, they usually compare every data member and treat two objects as equal if and only if all the corresponding members are equal. In line with this design philosophy, our Sales_data equality operator should compare the bookNo as well as the sales figures: { // in class scope friend bool operator==(const Sales_data&, const Sales_data&); friend bool operator!=(const Sales_data&, const Sales_data&); } inline bool operator==(const Sales_data& lhs, const Sales_data& rhs) { return lhs.isbn() == rhs.isbn() && lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue; } inline bool operator!=(const Sales_data& lhs, const Sales_data& rhs) { return !(lhs == rhs); } Relational OperatorsClasses for which the equality operator is defined also often (but not always) have relational operators. like < > <= >=. We might think that we’d define < similarly to compareIsbn . That function compared Sales_data objects by comparing their ISBNs. But results that are inconsistent with our definition of ==. Assignment OperatorsIn addition to the copy- and move-assignment operators that assign one object of the class type to another object of the same type, a class can define additional assignment operators that allow other types as the right-hand operand. As one example, in addition to the copy- and move-assignment operators, the library vector class defines a third assignment operator that takes a braced list of elements. We can use this operator as follows: vector<string> v; v = {"a", "an", "the"}; We can add this operator to our StrVec class: { //class scope StrVec& operator=(std::initializer_list<std::string>); } StrVec& StrVec::operator=(std::initializer_list<std::string> il) { // alloc_n_copy allocates space and copies elements from the given range auto data = alloc_n_copy(il.begin(), il.end()); free(); // destroy the elements in this object and free the space elements = data.first; // update data members to point to the new space first_free = cap = data.second; return *this; } Notice: In pervious version, the alloc_n_copy parameters are string*, but il.begin() return a const string*, we need change to: std::pair<std::string*, std::string*> StrVec::alloc_n_copy(const std::string* b, const std::string* e) This is a simple case to test our StrVec class: #include"StrVec.h" #include<iostream> int main() { StrVec p; p = { "a", "an", "the" }; for (size_t i = 0; i < p.size(); i++) { std::cout << *(p.begin() + i) << std::endl; } return 0; } Compound assignment operators are not required to be members. However, we prefer to define all assignments, including compound assignments, in the class. For consistency with the built-in compound assignment, these operators should return a reference to their left-hand operand. For example, here is the definition of the Sales_data compound-assignment operator: { // in class scope Sales_data& operator+=(Sales_data&); } inline Sales_data& Sales_data::operator+=(Sales_data& rhs) { this->units_sold += rhs.units_sold; this->revenue += rhs.revenue; return *this; } This is a simple case to test our Sales_data class: #include"Sales_data.h" #include<iostream> #include<iostream> int main() { std::string isbn = "1-4y35"; Sales_data b1(isbn, 80, 18.9); Sales_data b2(isbn, 20, 18.9); b1 += b2; std::cout << b1 << std::endl; return 0; } Subscript OperatorClasses that represent containers from which elements can be retrieved by position often define the subscript operator, operator[]. As an example, we’ll define subscript for StrVec. Consequently, it is also usually a good idea to define both const and nonconst versions of this operator. When applied to a const object, subscript should return a reference to const so that it is not possible to assign to the returned object. { // in class scope std::string operator[](std::size_t idx) { return elements[idx]; } const std::string operator[](std::size_t idx) const { return elements[idx]; } } We can use these operators similarly to how we subscript a vector or array. Because subscript returns a reference to an element, if the StrVec is nonconst, we can assign to that element; if we subscript a const object, we can’t. Increment and Decrement OperatorsThe increment (++) and decrement (–) operators are most often implemented for iterator classes. These operators let the class move between the elements of a sequence. There is no language requirement that these operators be members of the class. However, because these operators change the state of the object on which they operate, our preference is to make them members. For the built-in types, there are both prefix and postfix versions of the increment and decrement operators. Not surprisingly, we can define both the prefix and postfix instances of these operators for our own classes as well. We’ll look at the prefix versions first and then implement the postfix ones. Defining Prefix Increment/Decrement Operators{ // in class scope class_name& operator++(); class_name& operator--(); } Differentiating Prefix and Postfix OperatorsThere is one problem with defining both the prefix and postfix operators: Normal overloading cannot distinguish between these operators. The prefix and postfix versions use the same symbol, meaning that the overloaded versions of these operators have the same name. They also have the same number and type of operands. To solve this problem, the postfix versions take an extra (unused) parameter of type int. { // in class scope class_name& operator++(int); class_name& operator--(int); } Function-Call OperatorClasses that overload the call operator allow objects of its type to be used as if they were a function. Because such classes can also store state, they can be more flexible than ordinary functions.As a simple example, the following struct, named absInt, has a call operator that returns the absolute value of its argument: struct absInt { int operator()(int val) const { return val < 0 ? -val : val; } }; This class defines a single operation: the function-call operator. That operator takes an argument of type int and returns the argument’s absolute value. int i = -42; absInt absObj; // object that has a function-call operator int ui = absObj(i); // passes i to absObj.operator() Objects of classes that define the call operator are referred to as function objects. Such objects “act like functions” because we can call them. Like any other class, a function-object class can have additional members aside from operator(). Function-object classes often contain data members that are used to customize the operations in the call operator. As an example, we’ll define a class that prints a string argument. By default, our class will write to cout and will print a space following each string. We’ll also let users of our class provide a different stream on which to write and provide a different separator. We can define this class as follows: class PrintString { public: PrintString(ostream &o = cout, char c = ' '): os(o), sep(c) { } void operator()(const string &s) const { os << s << sep; } private: ostream &os; // stream on which to write char sep; // character to print after each output }; Lambdas Are Function ObjectsWhen we write a lambda, the compiler translates that expression into an unnamed object of an unnamed class. The classes generated from a lambda contain an overloaded function-call operator. For example, the lambda that we passed as the last argument to stable_sort: // sort words by size, but maintain alphabetical order for words of the same size std::stable_sort(words.begin(), words.end(), [](const std::string& a, const std::string& b) { return a.size() < b.size(); }); acts like an unnamed object of a class that would look something like class ShorterString { public: bool operator()(const string &s1, const string &s2) const{ return s1.size() < s2.size(); } }; If the lambda is declared as mutable, then the call operator is not const. As we’ve seen, when a lambda captures a variable by reference, it is up to the program to ensure that the variable to which the reference refers exists when the lambda is executed. Therefore, the compiler is permitted to use the reference directly without storing that reference as a data member in the generatedclass. In contrast, variables that are captured by value are copied into the lambda. As a result, classes generated from lambdas that capture variables by value have data members corresponding to each such variable. These classes also have a constructor to initialize these data members from the value of the captured variables. As an example,the lambda that we used to find the first string whose length was greater than or equal to a given bound: // get an iterator to the first element whose size() is >= sz auto wc = find_if(words.begin(), words.end(),[sz](const string &a)); would generate a class that looks something like class SizeComp { SizeComp(size_t n) : sz(n) {} // parameter for each captured variable // call operator with the same return type, parameters, and body as the lambda bool operator()(const string& s) const { return s.size() >= sz; } private: size_t sz; // a data member for each variable captured by value }; Library-Defined Function ObjectsThe standard library defines a set of classes that represent the arithmetic, relational, and logical operators. Each class defines a call operator that applies the named operation. For example, the plus class has a function-call operator that applies + to a pair of operands; the modulus class defines a call operator that applies the binary % operator; the equal_to class applies ==; and so on. plus<int> intAdd; // function object that can add two int values negate<int> intNegate; // function object that can negate an int value // uses intAdd::operator(int, int) to add 10 and 20 int sum = intAdd(10, 20); // equivalent to sum = 30 sum = intNegate(intAdd(10, 20)); // equivalent to sum = 30 // uses intNegate::operator(int) to generate -10 as the second parameter // to intAdd::operator(int, int) sum = intAdd(10, intNegate(10)); // sum = 0 Overloading, Conversions, and OperatorsConversion OperatorsA conversion operator is a special kind of member function that converts a value of a class type to a value of some other type. A conversion function typically has the general form: operator type() const; where type represents a type. Conversion operators can be defined for any type(other than void) that can be a function return type. Conversions to an array or a function type are not permitted. Conversions to pointer types—both data and function pointers—and to reference types are allowed. Conversion operators have no explicitly stated return type and no parameters, and they must be defined as member functions. Conversion operations ordinarily should not change the object they are converting. As a result, conversion operators usually should be defined as const members. As an example, we’ll define a small class that represents an integer in the range of 0 to 255: #pragma once #include <stdexcept> class SmallInt { public: SmallInt(int i = 0) :data(i) { if (data > 255 || data < 0) { throw std::out_of_range("out of range!"); } } operator int() const { return data; } private: int data; }; Our SmallInt class defines conversions to and from its type. The constructor converts values of arithmetic type to a SmallInt. The conversion operator convertsSmallInt objects to int. SmallInt si; si = 4; // implicitly converts 4 to SmallInt then calls SmallInt::operator= si + 3; // implicitly converts si to int followed by integer addition To prevent such problems, the new standard introduced explicit conversion explicit operator int() const { return data; } SmallInt si = 3; // ok: the SmallInt constructor is not explicit si + 3; // error: implicit is conversion required, but operator int is explicit static_cast<int>(si) + 3; // ok: explicitly request the conversion As with an explicit constructor, the compiler won’t (generally) use an explicit conversion operator for implicit conversions: Avoiding Ambiguous ConversionsIf a class has one or more conversions, it is important to ensure that there is only one way to convert from the class type to the target type. If there is more than one way to perform a conversion, it will be hard to write unambiguous code. // usually a bad idea to have mutual conversions between two class types struct B; struct A { A() = default; A(const B&); // converts a B to an A // other members }; struct B { operator A() const; // also converts a B to an A // other members }; A f(const A&); B b; A a = f(b); // error ambiguous: f(B::operator A()) // or f(A::A(const B&)) Because there are two ways to obtain an A from a B, the compiler doesn’t know which conversion to run; the call to f is ambiguous. This call can use the A constructor that takes a B, or it can use the B conversion operator that converts a B to an A. Because these two functions are equally good, the call is in error. Function Matching and Overloaded OperatorsOverloaded operators are overloaded functions. Normal function matching is used to determine which operator—built-in or overloaded—to apply to a given expression. However, when an operator function is used in an expression, the set of candidate functions is broader than when we call a function using the call operator. If a has a class type, the expression a sym b might be: a.operatorsym (b); // a has operatorsym as a member function operatorsym(a, b); // operatorsym is an ordinary function When we use an overloaded operator with an operand of class type, the candidate functions include ordinary nonmember versions of that operator, as well as the built-in versions of the operator. Moreover, if the left-hand operand has class type, the overloaded versions of the operator, if any, defined by that class are also included. When we call a named function, member and nonmember functions with the same name do not overload one another. There is no overloading because the syntax we use to call a named function distinguishes between member and nonmember functions. When a call is through an object of a class type (or through a reference or pointer to such an object), then only the member functions of that class are considered. When we use an overloaded operator in an expression, there is nothing to indicate whether we’re using a member or nonmember function. Therefore, both member and nonmember versions must be considered. As an example, we’ll define an addition operator for our SmallInt class: friend SmallInt operator+(const SmallInt&, const SmallInt&); We can use this class to add two SmallInts, but we will run into ambiguity problems if we attempt to perform mixed-mode arithmetic: SmallInt s1, s2; SmallInt s3 = s1 + s2; // uses overloaded operator+ int i = s3 + 0; // error: ambiguous The first addition uses the overloaded version of + that takes two SmallInt values. The second addition is ambiguous, because we can convert 0 to a SmallInt and use the SmallInt version of +, or convert s3 to int and use the built-in addition operator on ints.","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch9) Copy Control","slug":"Cpp_c09","date":"2021-12-09T13:52:45.000Z","updated":"2023-04-21T15:56:18.077Z","comments":true,"path":"2021/12/09/Cpp_c09/","link":"","permalink":"https://noahbishop.github.io/2021/12/09/Cpp_c09/","excerpt":"","text":"Copy ControlAs we see in chapter 3, each class defines a new type and defines the operations that objects of that type can perform. In that chapter, we also learned that classes can define constructors, which control what happens when objects of the class type are created.In this chapter we’ll learn how classes can control what happens when objects of the class type are copied, assigned, moved, or destroyed. Classes control these actions through special member functions: the copy constructor, move constructor, copy-assignment operator, move-assignment operator, and destructor. Copy, Assign, and DestroyThe Copy ConstructorA constructor is the copy constructor if its first parameter is a reference to the class type and any additional parameters have default values: class Temp { public: Temp() = default; // default constructor Temp(const Temp&);// copy constructor }; For reasons we’ll explain shortly, the first parameter must be a reference type. That parameter is almost always a reference to const, although we can define the copy constructor to take a reference to nonconst. The copy constructor is used implicitly in several circumstances. Hence, the copy constructor usually should not be explicit. When we do not define a copy constructor for a class, the compiler synthesizes one for us. Unlike the synthesized default constructor, a copy constructor is synthesized even if we define other constructors. The type of each member determines how that member is copied: Members of class type are copied by the copy constructor for that class; members of built-in type are copied directly. Although we cannot directly copy an array, the synthesized copy constructor copies members of array type by copying each element. Elements of class type are copied by using the elements’ copy constructor. As an example, the synthesized copy constructor for our Sales_data class is equivalent to: class Sales_data { public: // other members and constructors as before // declaration equivalent to the synthesized copy constructor //copy constructors Sales_data(const Sales_data& org) : bookNo(org.bookNo), units_sold(org.units_sold), revenue(org.revenue) { //empty } } We are now in a position to fully understand the differences between direct initialization and copy initialization: When we use direct initialization, we are asking the compiler to use ordinary function matching to select the constructor that best matches the arguments we provide. When we use copy initialization, we are asking the compiler to copy the right-hand operand into the object being created, converting that operand if necessary. Copy initialization happens not only when we define variables using an =, but also when we: Pass an object as an argument to a parameter of nonreference type Return an object from a function that has a nonreference return type Brace initialize the elements in an array or the members of an aggregate class Some class types also use copy initialization for the objects they allocate. For example, the library containers copy initialize their elements when we initialize the container, or when we call an insert or push member. By contrast, elements created by an emplace member are direct initialized. The Copy-Assignment OperatorAs with the copy constructor, the compiler synthesizes a copy-assignment operator if the class does not define its own. Before we look at the synthesized assignment operator, we need to know a bit about overloaded operators, which we cover in detail in next chapter. Overloaded operators are functions that have the name operator followed by the symbol for the operator being defined. Hence, the assignment operator is a function named operator=. Like any other function, an operator function has a return type and a parameter list. The parameters in an overloaded operator represent the operands of the operator. Some operators, assignment among them, must be defined as member functions. When an operator is a member function, the left-hand operand is bound to the implicit this parameter. The right-hand operand in a binary operator, such as assignment, is passed as an explicit parameter. The copy-assignment operator takes an argument of the same type as the class: Temp& operator=(const Temp& org); To be consistent with assignment for the built-in types, assignment operators usually return a reference to their left-hand operand. It is also worth noting that the library generally requires that types stored in a container have assignment operators that return a reference to the left-hand operand. Just as it does for the copy constructor, the compiler generates a synthesized copy-assignment operator for a class if the class does not define its own. Otherwise, it assigns each nonstatic member of the right-hand object to the corresponding member of the left-hand object using the copy-assignment operator for the type of that member. Array members are assigned by assigning each element of the array. The synthesized copy-assignment operator returns a reference to its left-hand object. Temp& Temp::operator=(const Temp& org) { data = org.data + 2; return *this; } #include"Temp.h" int main() { Temp p(10); //p.data = 10 Temp q = p; //q.data = 11 Temp r; r = p; //r.data = 12 return 0; } The DestructorThe destructor operates inversely to the constructors: Constructors initialize the nonstatic data members of an object and may do other work; destructors do whatever work is needed to free the resources used by an object and destroy the nonstatic data members of the object. The destructor is a member function with the name of the class prefixed by a tilde ~. It has no return value and takes no parameters: class Temp{ public: ~Temp(); // destructor } Because it takes no parameters, it cannot be overloaded. There is always only one destructor for a given class. Just as a constructor has an initialization part and a function body, a destructor has a function body and a destruction part. In a constructor, members are initialized before the function body is executed, and members are initialized in the same order as they appear in the class. In a destructor, the function body is executed first and then the members are destroyed. Members are destroyed in reverse order from the order in which they were initialized. The function body of a destructor does whatever operations the class designer wishes to have executed subsequent to the last use of an object. Typically, the destructor frees resources an object allocated during its lifetime. In a destructor, there is nothing akin to the constructor initializer list to control how members are destroyed; the destruction part is implicit. What happens when a member is destroyed depends on the type of the member. Members of class type are destroyed by running the member’s own destructor. The built-in types do not have destructors, so nothing is done to destroy members of built-in type. The destructor is used automatically whenever an object of its type is destroyed: Variables are destroyed when they go out of scope. Members of an object are destroyed when the object of which they are a part is destroyed. Elements in a container—whether a library container or an array—are destroyed when the container is destroyed. Dynamically allocated objects are destroyed when the delete operator is applied to a pointer to the object. Temporary objects are destroyed at the end of the full expression in which the temporary was created. The Rule of Three/FiveAs we’ve seen, there are three basic operations to control copies of class objects: the copy constructor, copy-assignment operator, and destructor. Moreover, in section 6, under the new standard, a class can also define a move constructor and move-assignment operator. There is no requirement that we define all of these operations: We can define one or two of them without having to define all of them. However, ordinarily these operations should be thought of as a unit. In general, it is unusual to need one without needing to define them all. One rule of thumb to use when you decide whether a class needs to define its own versions of the copy-control members is to decide first whether the class needs a destructor. Often, the need for a destructor is more obvious than the need for the copy constructor or assignment operator. If the class needs a destructor, it almost surely needs a copy constructor and copy-assignment operator as well. As a example, we need allocate allocates dynamic memory to save a string in the class, when we call constructor. We also need free the memory when call destructor. Temp::~Temp() { delete s; } In this version of the class, the memory allocated in the constructor will be freed when a object is destroyed. Unfortunately, we have introduced a serious bug! This version of the class uses the synthesized versions of copy and assignment. Those functions copy the pointer member, meaning that multiple objects may be pointing to the same memory: #pragma once #include <string> class Temp { public: Temp() = default; // default constructor Temp(int num) :data(num), s(new std::string(10, 'A')) { } ~Temp(); // destructor const int a = 10; private: int data; std::string* s; }; Temp::~Temp() { delete s; } #include"Temp.h" int main() { Temp p(10); //p->s = AAAAAAAAAA Temp q = p; //q->s = AAAAAAAAAA // p.s and q.s point to same address return 0; } This code will delete that pointer twice. The second rule of thumb: If a class needs a copy constructor, it almost surely needs a copy-assignment operator. And vice versa—if the class needs an assignment operator, it almost surely needs a copy constructor as well. Preventing CopiesAlthough most classes should (and generally do) define a copy constructor and a copy-assignment operator, for some classes, there really is no sensible meaning for these operations. In such cases, the class must be defined so as to prevent copies or assignments from being made. For example, the iostream classes prevent copying to avoid letting multiple objects write to or read from the same IO buffer. It might seem that we could prevent copies by not defining the copy-control members. However, this strategy doesn’t work: If our class doesn’t define these operations, the compiler will synthesize them. Under the new standard, we can prevent copies by defining the copy constructor and copy-assignment operator as deleted functions. A deleted function is one that is declared but may not be used in any other way. We indicate that we want to define a function as deleted by following its parameter list with = delete: #pragma once class NoCopy { public: NoCopy() = default; ~NoCopy() = default; NoCopy(const NoCopy&) = delete; // no copy private: int a = 10; }; It is worth noting that we did not delete the destructor. If the destructor is deleted, then there is no way to destroy objects of that type. Copy Control and Resource ManagementOrdinarily, classes that manage resources that do not reside in the class must define the copy-control members. As we saw in previous, such classes will need destructors to free the resources allocated by the object. Once a class needs a destructor, it almost surely needs a copy constructor and copy-assignment operator as well. In order to define these members, we first have to decide what copying an object of our type will mean. In general, we have two choices: We can define the copy operations to make the class behave like a value or like a pointer. Classes that behave like values have their own state. When we copy a value like object, the copy and the original are independent of each other. Changes made to the copy have no effect on the original, and vice versa. Classes that act like pointers share state. When we copy objects of such classes, the copy and the original use the same underlying data. Changes made to the copy also change the original, and vice versa. To illustrate these two approaches, we’ll define the copy-control members for the TestStr class. First, we’ll make the class act like a value; then we’ll reimplement the class making it behave like a pointer. Classes That Act Like ValuesTo provide value like behavior, each object has to have its own copy of the resource that the class manages. That means each TestStr object must have its own copy of the string to which s points. To implement value like behavior TestStr needs: A copy constructor that copies the string, not just the pointer A destructor to free the string A copy-assignment operator to free the object’s existing string and copy the string from its right-hand operand #pragma once #include<string> class TestStr { public: // constructor TestStr(const std::string& os = "") : s(new std::string(os)) {} // copy constructor TestStr(const TestStr& org) : s(new std::string(*(org.s))) { // empty } // copy-assignment operator TestStr& operator=(const TestStr& org); // destructor ~TestStr() { delete s; } private: std::string* s; }; TestStr& TestStr::operator=(const TestStr& org) { auto newp = new std::string(*org.s); delete s; s = newp; return *this; } In operator= we write auto newp = new std::string(*org.s); and the s = newp, could we straightforward use s = new std::string(*org.s);, the answer is certainly not. To illustrate the importance of guarding against self-assignment, consider what would happen if run this code: TestStr s; s = s; If there are same object, first free string, and then create a string by a string that has been free. Defining Classes That Act Like PointersFor our TestStr class to act like a pointer, we need the copy constructor and copy assignment operator to copy the pointer member, not the string to which that pointer points. Our class will still need its own destructor to free the memory allocated by the constructor that takes a string. In this case, though, the destructor cannot unilaterally free its associated string. It can do so only when the last TestStr pointing to that string goes away. The easiest way to make a class act like a pointer is to use shared_ptrs to manage the resources in the class. However, sometimes we want to manage a resource directly. In such cases, it can be useful to use a reference count. Reference counting works as follows: In addition to initializing the object, each constructor (other than the copy constructor) creates a counter. This counter will keep track of how many objects share state with the object we are creating. When we create an object, there is only one such object, so we initialize the counter to 1. The copy constructor does not allocate a new counter; instead, it copies the data members of its given object, including the counter. The copy constructor increments this shared counter, indicating that there is another user of that object’s state. The destructor decrements the counter, indicating that there is one less user of the shared state. If the count goes to zero, the destructor deletes that state. The copy-assignment operator increments the right-hand operand’s counter and decrements the counter of the left-hand operand. The only wrinkle is deciding where to put the reference count. The counter cannot be a direct member of a TestStr object. To see why, consider what happens in the following example: TestStr p; TestStr q(p); TestStr r(p); If the reference count is stored in each object, how can we update it correctly when r is created? We could increment the count in p and copy that count into r, but how would we update the counter in q? One way to solve this problem is to store the counter in dynamic memory. When we create an object, we’ll also allocate a new counter. When we copy or assign an object, we’ll copy the pointer to the counter. That way the copy and the original will point to the same counter. #pragma once #include<string> class TestStr { public: // constructor TestStr(const std::string& os = "") : s(new std::string(os)), counter(new std::size_t(1)) { //empty } // copy constructor TestStr(const TestStr& org) : s(org.s), counter(org.counter) { // counter++ (*counter)++; } // copy-assignment operator TestStr& operator=(const TestStr& org); // destructor ~TestStr() { if (--(*counter) == 0) { delete s; } } private: std::string* s; std::size_t* counter; }; TestStr& TestStr::operator=(const TestStr& org) { // increment the use count of the right-hand operand (*org.counter)++; // if no other users if (--(*counter) == 0) { delete s; delete counter; } // copy data from org into this object s = org.s; counter = org.counter; return *this; } SwapIn addition to defining the copy-control members, classes that manage resources often also define a function named swap. Defining swap is particularly important for classes that we plan to use with algorithms that reorder elements. Such algorithms call swap whenever they need to exchange two elements. #include<iostream> int main() { int a = 1, b = 2; std::swap(a, b); std::cout << a << " " << b << std::endl; // 2 1 return 0; } If a class defines its own swap, then the algorithm uses that class-specific version. Otherwise, it uses the swap function defined by the library. Although, as usual, we don’t know how swap is implemented, conceptually it’s easy to see that swapping two objects involves a copy and two assignments. For example, code to swap two objects of our value-like TestStr class might look something like: TestStr temp = v1; // make a temporary copy of the value of v1 v1 = v2; // assign the value of v2 to v1 v2 = temp; // assign the saved value of v1 to v2 In this swap function, we create a temporary copy of the value of v1. Copying a value-like TestStr allocates a new string and copies the string to which the TestStr points. In principle, none of this memory allocation is necessary, we can just do this: string *temp = v1.ps; // make a temporary copy of the pointer in v1.ps v1.ps = v2.ps; // assign the pointer in v2.ps to v1.ps v2.ps = temp; // assign the saved pointer in v1.ps to v2.ps Writing Our Own swap FunctionWe can override the default behavior of swap by defining a version of swap that operates on our class. The typical implementation of swap is: // write in TestStr.h file // note in the case wo rename TestStr to TestStr_swap class TestStr { public: friend void swap(TestStr_swap&, TestStr_swap&); // same as before }; // write in TestStr.cpp file void swap(TestStr_swap& p1, TestStr_swap& p2) { std::swap(p1.s, p2.s); } NOTICE: If you use vs2019 create a class, don’t write methods in *.h file, plz write in the corresponding *.cpp file. We start by declaring swap as a friend to give it access to TestStr (private) data members. Then the body of swap calls swap on each of the data members of the given object. Using swap in Assignment OperatorsClasses that define swap often use swap to define their assignment operator. These operators use a technique known as copy and swap. This technique swaps the left-hand operand with a copy of the right-hand operand: // note this version pass value rather than reference TestStr_swap& TestStr_swap::operator=(TestStr_swap org) { swap(*this, org); return *this; } In the body of the assignment operator, we call swap, which swaps the data members of org with those in *this. Because we pass org by value the org will automatically call its destroy function when this function end. A Copy-Control ExampleAlthough copy control is most often needed for classes that allocate resources, resource management is not the only reason why a class might need to define these members. Some classes have bookkeeping or other actions that the copy-control members must perform. As an example of a class that needs copy control in order to do some bookkeeping, we’ll sketch out two classes that might be used in a mail-handling application. These classes, Message and Folder, represent, respectively, email (or other kinds of) messages, and directories in which a message might appear. Each Message can appear in multiple Folders. However, there will be only one copy of the contents of any given Message. That way, if the contents of a Message are changed, those changes will appear when we view that Message from any of its Folders. To keep track of which Messages are in which Folders, each Message will store a set of pointers to the Folders in which it appears, and each Folder will contain a set of pointers to its Messages. Our Message class will provide save and remove operations to add or remove a Message from a specified Folder. To create a new Message, we will specify the contents of the message but no Folder. To put a Message in a particular Folder, we must call save. When we destroy a Message, that Message no longer exists. Therefore, destroying a Message must remove pointers to that Message from the Folders that had contained that Message. The Message Class// in Message.h #pragma once #include<string> #include<set> #include"Folder.h" class Folder; class Message { friend class Folder; public: friend void swap(Message&, Message&); // constructor explicit Message(std::string s = "") :contents(s) { //empty } // copy constructor Message(Message& org); // copy assignment Message& operator=(const Message& org); // destructor ~Message(); // add/remove this Message from the specified Folder's set of messages void save(Folder&); void remove(Folder&); // get message std::string& getMsg(); private: std::string contents; std::set<Folder*> folders; // add this Message to the Folders that point to the parameter void add_to_Folders(const Message&); // remove this Message from every Folder in folders void remove_from_Folders(); }; // in Message.cpp #include "Message.h" Message::Message(Message& org) :contents(org.contents), folders(org.folders) { // add this Message to the Folders that point to org add_to_Folders(org); } Message& Message::operator=(const Message& org) { remove_from_Folders(); contents = org.contents; folders = org.folders; add_to_Folders(*this); return *this; } Message::~Message() { // remove message pointer in folder remove_from_Folders(); } void Message::save(Folder& f) { // add the given Folder to our list of Folders folders.insert(&f); // add this Message to f's set of Messages f.addMsg(this); } void Message::remove(Folder& f) { // remove the given Folder from our list of Folders folders.erase(&f); // remove this Message from f's set of Messages f.remMsg(this); } std::string& Message::getMsg() { return this->contents; } void Message::add_to_Folders(const Message& org) { for (auto& f : org.folders) { f->addMsg(this); } } void Message::remove_from_Folders() { for (auto& f : folders) { f->remMsg(this); } } void swap(Message& m1, Message& m2) { m1.remove_from_Folders(); m2.remove_from_Folders(); std::swap(m1.contents, m2.contents); std::swap(m1.folders, m2.folders); m1.add_to_Folders(m1); m2.add_to_Folders(m2); } The Folder class// in Folder.h #pragma once #include<set> #include"Message.h" #include<iostream> class Message; class Folder { friend class Message; friend void listall(Folder& f); public: Folder() = default; ~Folder(); private: std::set<Message*> messages; // add and remove message to folder void addMsg(Message*); void remMsg(Message*); }; // in Folder.cpp #include "Folder.h" Folder::~Folder() { } void Folder::addMsg(Message* m) { messages.insert(m); } void Folder::remMsg(Message* m) { messages.erase(m); } void listall(Folder& f) { for (auto& m : f.messages) { std::cout << m->getMsg() << " "; } std::cout << std::endl; } Classes That Manage Dynamic MemorySome classes need to allocate a varying amount of storage at run time. Such classes often can (and if they can, generally should) use a library container to hold their data. For example, our StrBlob class uses a vector to manage the underlying storage for its elements. However, this strategy does not work for every class; some classes need to do their own allocation. Such classes generally must define their own copy-control members to manage the memory they allocate. As an example, we’ll implement a simplification of the library vector class. Among the simplifications we’ll make is that our class will not be a template. Instead, our class will hold strings. Thus, we’ll call our class StrVec. Class StrVec designRecall that the vector class stores its elements in contiguous storage. To obtain acceptable performance, vector pre-allocates enough storage to hold more elements than are needed. Each vector member that adds elements checks whether there is space available for another element. If so, the member constructs an object in the next available spot. If there isn’t space left, then the vector is reallocated: The vector obtains new space, moves the existing elements into that space, frees the old space, and adds the new element. Each StrVec will have three pointers into the space it uses for its elements: elements, which points to the first element in the allocated memory first_free, which points just after the last actual element cap, which points just past the end of the allocated memory In addition to these pointers, StrVec will have a member named alloc that is an allocator<string>. The alloc member will allocate the memory used by a StrVec. Our class will also have four utility functions: alloc_n_copy will allocate space and copy a given range of elements. free will destroy the constructed elements and deallocate the space. chk_n_alloc will ensure that there is room to add at least one more element to the StrVec. reallocate will reallocate the StrVec when it runs out of space. Key: the reallocate implementBefore we write the reallocate member, we should think a bit about what it must do. This function will Allocate memory for a new, larger array of strings Construct the first part of that space to hold the existing elements Destroy the elements in the existing memory and deallocate that memory Copying the data in these strings is unnecessary. Our StrVec performance will be much better if we can avoid the overhead of allocating and deallocating the strings themselves each time we reallocate. We can avoid copying the strings by using two facilities introduced by the new library. First, several of the library classes, including string, define so-called “move constructors”. For string, we can imagine thateach string has a pointer to an array of char. Presumably the string move constructor copies the pointer rather than allocating space for and copying the characters themselves. The second facility we’ll use is a library function named move, which is defined in the utility header. Class definition and check// in StrVec.h file #pragma once #include<memory> #include<string> class StrVec { public: StrVec() :elements(nullptr), first_free(nullptr), cap(nullptr) { //empty } StrVec(const StrVec&); StrVec& operator=(const StrVec&); ~StrVec(); void push_back(std::string&); size_t size(); size_t capacity(); std::string* begin() const; std::string* end() const; std::string& at(size_t idx); private: // private methods void free(); void chk_n_alloc(); void reallocate(); std::pair< std::string*, std::string*> alloc_n_copy(std::string*, std::string*); // private attributes std::allocator<std::string> alloc; std::string* elements; std::string* first_free; std::string* cap; }; // in StrVec.cpp file #include "StrVec.h" StrVec::StrVec(const StrVec& v) { auto newdata = alloc_n_copy(v.begin(), v.end()); elements = newdata.first; first_free = cap = newdata.second; } StrVec& StrVec::operator=(const StrVec& v) { auto newdata = alloc_n_copy(v.begin(), v.end()); free(); elements = newdata.first; first_free = cap = newdata.second; return *this; } StrVec::~StrVec() { free(); } void StrVec::push_back(std::string& s) { chk_n_alloc(); alloc.construct(first_free++, s); } size_t StrVec::size() { return first_free - elements; } size_t StrVec::capacity() { return cap - elements; } std::string* StrVec::begin() const { return elements; } std::string* StrVec::end() const { return first_free; } std::string& StrVec::at(size_t idx) { return *(elements + idx); } void StrVec::free() { if (elements) { alloc.deallocate(elements, cap - elements); } } void StrVec::chk_n_alloc() { if (size() == capacity()) { reallocate(); } } void StrVec::reallocate() { auto newcap = size() ? 2 * size() : 1; auto newdata = alloc.allocate(newcap); auto dest = newdata, org = elements; for (auto i = 0; i != size(); i++) { alloc.construct(dest++, std::move(*org++)); } free(); elements = newdata; cap = elements + newcap; first_free = dest; } std::pair<std::string*, std::string*> StrVec::alloc_n_copy(std::string* b, std::string* e) { auto data = alloc.allocate(e - b); return { data, std::uninitialized_copy(b,e,data) }; } #include<iostream> #include"StrVec.h" #include<string> void check(StrVec& m) { for (size_t i = 0; i < m.size(); i++) { std::cout << m.at(i) << std::endl; } std::cout << "size: " << m.size() << std::endl; std::cout << "cap: " << m.capacity() << std::endl; } int main() { StrVec m1; for (size_t i = 0; i < 5; i++) { std::string s = std::to_string(i); m1.push_back(s); } StrVec m2; m2 = m1; StrVec m3(m1); check(m1); check(m2); check(m3); return 0; } Moving ObjectsOne of the major features in the new standard is the ability to move rather than copy an object. Copies are made in many circumstances. In some of these circumstances, an object is immediately destroyed after it is copied. In those cases, moving, rather than copying, the object can provide a significant performance boost. Rvalue ReferencesTo support move operations, the new standard introduced a new kind of reference, an rvalue reference. An rvalue reference is a reference that must be bound to an rvalue. An rvalue reference is obtained by using && rather than &. As we’ll see, rvalue references have the important property that they may be bound only to an object that is about to be destroyed. As a result, we are free to “move” resources from an rvalue reference to another object. Like any reference, an rvalue reference is just another name for an object. As we know, we cannot bind regular references—which we’ll refer to as lvalue references when we need to distinguish them from rvalue references—to expressions that require a conversion, to literals, or to expressions that return an rvalue. int i = 42; int &r = i; // ok: r refers to i int &&rr = i; // error: cannot bind an rvalue reference to an lvalue int &r2 = i * 42; // error: i * 42 is an rvalue const int &r3 = i * 42; // ok: we can bind a reference to const to an rvalue int &&rr2 = i * 42; // ok: bind rr2 to the result of the multiplication Looking at the list of lvalue and rvalue expressions, it should be clear that lvalues and rvalues differ from each other in an important manner: Lvalues have persistent state, whereas rvalues are either literals or temporary objects created in the course of evaluating expressions. Because rvalue references can only be bound to temporaries, we know that: The referred-to object is about to be destoryed There can be no other users of that object Although we rarely think about it this way, a variable is an expression with one operand and no operator. Like any other expression, a variable expression has the lvalue/rvalue property. Variable expressions are lvalues. It may be surprising, but as a consequence, we cannot bind an rvalue reference to a variable defined as an rvalue reference type: int &&rr1 = 42; // ok: literals are rvalues int &&rr2 = rr1; // error: the expression rr1 is an lvalue! Given our previous observation that rvalues represent ephemeral objects, it should not be surprising that a variable is an lvalue. After all, a variable persists until it goes out of scope. Although we cannot directly bind an rvalue reference to an lvalue, we can explicitly cast an lvalue to its corresponding rvalue reference type. We can also obtain an rvalue reference bound to an lvalue by calling a new library function named move, which is defined in the utility header. Calling move tells the compiler that we have an lvalue that we want to treat as if it were an rvalue. It is essential to realize that the call to move promises that we do not intend to use rr1 again except to assign to it or to destroy it. After a call to move, we cannot make any assumptions about the value of the moved-from object. int &&rr3 = std::move(rr1); // ok Move Constructor and Move AssignmentLike the string class (and other library classes), our own classes can benefit from being able to be moved as well as copied. To enable move operations for our own types, we define a move constructor and a move-assignment operator. These members are similar to the corresponding copy operations, but they “steal” resources from their given object rather than copy them. In addition to moving resources, the move constructor must ensure that the moved from object is left in a state such that destroying that object will be harmless. In particular, once its resources are moved, the original object must no longer point to those moved resources—responsibility for those resources has been assumed by the newly created object. As an example, we’ll define the StrVec move constructor to move rather than copy the elements from one StrVec to another: StrVec::StrVec(StrVec&& v) :elements(v.elements), first_free(v.first_free), cap(v.cap) { v.elements = v.first_free = v.cap = nullptr; } Unlike the copy constructor, the move constructor does not allocate any new memory; it takes over the memory in the given StrVec. Having taken over the memory from its argument, the constructor body sets the pointers in the given object to nullptr. Because a move operation executes by “stealing” resources, it ordinarily does not itself allocate any resources. As a result, move operations ordinarily will not throw any exceptions. When we write a move operation that cannot throw, we should inform the library of that fact. As we’ll see, unless the library knows that our move constructor won’t throw, it will do extra work to cater to the possibility that moving an object of our class type might throw. One way inform the library is to specify noexcept on our constructor. Understanding why noexcept is needed can help deepen our understanding of how the library interacts with objects of the types we write. We need to indicate that a move operation doesn’t throw because of two interrelated facts: First, although move operations usually don’t throw exceptions, they are permitted to do so. Second, the library containers provide guarantees as to what they do if an exception happens. As one example, vector guarantees that if an exception happens when we call push_back, the vector itself will be left unchanged. As we’ve just seen, moving an object generally changes the value of the moved from object. If reallocation uses a move constructor and that constructor throws an exception after moving some but not all of the elements, there would be a problem. The moved-from elements in the old space would have been changed, and the unconstructed elements in the new space would not yet exist. In this case, vector would be unable to meet its requirement that the vector is left unchanged. The move-assignment operator does the same work as the destructor and the move constructor. As with the move constructor, if our move-assignment operator won’t throw any exceptions, we should make it noexcept. Like a copy-assignment operator, a move-assignment operator must guard against self-assignment: StrVec& StrVec::operator=(StrVec&& org) noexcept { if (this != &org) { free(); elements = org.elements; first_free = org.first_free; cap = org.cap; org.elements = org.first_free = org.cap = nullptr; } return *this; } As it does for the copy constructor and copy-assignment operator, the compiler will synthesize the move constructor and move-assignment operator. However, the conditions under which it synthesizes a move operation are quite different from those in which it synthesizes a copy operation. The reallocate member of StrVec used a for loop to call construct to copy the elements from the old memory to the new. As an alternative to writing that loop, it would be easier if we could call uninitialized_copy to construct the newly allocated space. However, uninitialized_copy does what it says: It copies the elements. There is no analogous library function to “move” objects into unconstructed memory. Instead, the new library defines a move iterator adaptor. A move iterator adapts its given iterator by changing the behavior of the iterator’s dereference operator. Ordinarily, an iterator dereference operator returns an lvalue reference to the element. Unlike other iterators, the dereference operator of a move iterator yields an rvalue reference. void StrVec::reallocate() { auto newcap = size() ? 2 * size() : 1; auto first = alloc.allocate(newcap); auto last = uninitialized_copy(make_move_iterator(begin()), make_move_iterator(end()), first); free(); elements = first; cap = elements + newcap; first_free = last; } Rvalue References and Member FunctionsMember functions other than constructors and assignment can benefit from providing both copy and move versions. Such move-enabled members typically use the same parameter pattern as the copy/move constructor and the assignment operators—one version takes an lvalue reference to const, and the second takes an rvalue reference to nonconst. For example, the library containers that define push_back provide two versions: one that has an rvalue reference parameter and the other a const lvalue reference. Assuming X is the element type, these containers define: void push_back(const X&); // copy: binds to any kind of X void push_back(X&&); // move: binds only to modifiable rvalues of type X Note in our test program, first use std::string s = std::to_string(i); and then m1.push_back(s);. after we write this version of push_back that parameter is a rvalue. We can abbreviation previous two statements to m1.push_back(std::to_string(i));. void StrVec::push_back(std::string&& s) { chk_n_alloc(); alloc.construct(first_free++, std::move(s)); }","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch8) Dynamic Memory","slug":"Cpp_c08","date":"2021-12-08T13:52:45.000Z","updated":"2023-04-21T15:52:42.787Z","comments":true,"path":"2021/12/08/Cpp_c08/","link":"","permalink":"https://noahbishop.github.io/2021/12/08/Cpp_c08/","excerpt":"","text":"Dynamic MemoryThe programs we’ve written so far have used objects that have well-defined lifetimes. Global objects are allocated at program start-up and destroyed when the program ends. Local, automatic objects are created and destroyed when the block in which they are defined is entered and exited. Local static objects are allocated before their first use and are destroyed when the program ends. In addition to supporting automatic and static objects, C++ lets us allocate objects dynamically. Dynamically allocated objects have a lifetime that is independent of where they are created; they exist until they are explicitly freed. Our programs have used only static or stack memory. Static memory is used for local static objects, for class static data members, and for variables defined outside any function. Stack memory is used for no-static objects defined inside functions. Objects allocated in static or stack memory are automatically created and destroyed by the compiler. Stack objects exist only while the block in which they are defined is executing; static objects are allocated before they are used, and they are destroyed when the program ends. In addition to static or stack memory, every program also has a pool of memory that it can use. This memory is referred to as the free store or heap. Programs use the heap for objects that they dynamically allocate—that is, for objects that the program allocates at run time. The program controls the lifetime of dynamic objects; our code must explicitly destroy such objects when they are no longer needed. Dynamic Memory and Smart PointersIn C++, dynamic memory is managed through a pair of operators: new, which allocates, and optionally initializes, an object in dynamic memory and returns a pointer to that object; and delete, which takes a pointer to a dynamic object, destroys that object, and frees the associated memory. Dynamic memory is problematic because it is surprisingly hard to ensure that we free memory at the right time. Either we forget to free the memory—in which case we have a memory leak—or we free the memory when there are still pointers referring to that memory—in which case we have a pointer that refers to memory that is no longer valid. To make using dynamic memory easier (and safer), the new library provides two smart pointer types that manage dynamic objects. A smart pointer acts like a regular pointer with the important exception that it automatically deletes the object to which it points. The new library defines two kinds of smart pointers that differ in how they manage their underlying pointers: shared_ptr, which allows multiple pointers to refer to the same object, and unique_ptr, which “owns” the object to which it points. The library also defines a companion class named weak_ptr that is a weak reference to an object managed by a shared_ptr. All three are defined in the memory header. The shared_ptr ClassLike vectors, smart pointers are templates. Therefore, when we create a smart pointer, we must supply additional information—in this case, the type to which the pointer can point. As with vector, we supply that type inside angle brackets that follow the name of the kind of smart pointer we are defining: // shared_ptr that can point at a string shared_ptr<string> s_ptr1; // shared_ptr that can point at a vector of int shared_ptr<vector<int>> s_ptr2; A default initialized smart pointer holds a null pointer. We use a smart pointer in ways that are similar to using a pointer. Dereferencing a smart pointer returns the object to which the pointer points. When we use a smart pointer in a condition, the effect is to test whether the pointer is null: // if p1 is not null, check whether it's the empty string if (s_ptr1 && s_ptr1->empty()) { // if so, dereference p1 to assign a new value to that string *s_ptr1 = "hello"; } Next table lists operations common to shared_ptr and unique_ptr. member definition shared_ptr ptr Null smart pointer unique_ptr ptr Null smart pointer ptr as a condition check if ptr point to an object *ptr dereference ptr get object ptr->member equal to (*ptr).member ptr.get() Returns the stored pointer. swap(ptr1,ptr2) swap pointer ptr1 and ptr2 ptr1.swap(ptr2) swap pointer ptr1 and ptr2 This table show the particular operation for shared_ptr: member definition make_shared(args) Allocates and constructs an object of type T passing args to its constructor. shared_ptr ptr(q) p is a copy of shared_ptr q p=q assign to p p.unique() Check if unique p.use_count() Returns the number of shared_ptr objects that share The safest way to allocate and use dynamic memory is to call a library function named make_shared. This function allocates and initializes an object in dynamic memory and returns a shared_ptr that points to that object. Like the smart pointers, make_shared is defined in the memory header. When we call make_shared, we must specify the type of object we want to create. We do so in the same way as we use a template class, by following the function name with a type enclosed in angle brackets: // shared_ptr that points to an int with value 42 auto ptr1 = make_shared<int>(10); // shared_ptr that points to a string with value HHHHH auto ptr2 = make_shared<string>(5, 'H'); // shared_ptr that points to a string with value HELLO shared_ptr<string> ptr3 = make_shared<string>("HELLO"); // output: 10 HHHHH HELLO cout << *ptr1 << " " << *ptr2 << " " << *ptr3 << endl; Like the sequential-container emplace members, make_shared uses its arguments to construct an object of the given type. Of course, ordinarily we use auto to make it easier to define an object to hold the result of make_shared: When we copy or assign a shared_ptr, each shared_ptr keeps track of how many other shared_ptrs point to the same object: // copy shared_ptr // ptr3 and ptr4 point to the same object // object to which ptr3 and ptr4 point has two users auto ptr4 = ptr3; We can think of a shared_ptr as if it has an associated counter, usually referred to as a reference count. Whenever we copy a shared_ptr, the count is incremented. The counter is decremented when we assign a new value to the shared_ptr and when the shared_ptr itself is destroyed, such as when a local shared_ptr goes out of scope. Once a shared_ptr’s counter goes to zero, the shared_ptr automatically frees the object that it manages: auto r = make_shared<int>(42); // int to which r points has one user r = q; // assign to r, making it point to a different address // increase the use count for the object to which q points // reduce the use count of the object to which r had pointed // the object r had pointed to has no users; that object is automatically freed When the last shared_ptr pointing to an object is destroyed, the shared_ptr class automatically destroys the object to which that shared_ptr points. It does so through another special member function known as a destructor. Analogous to its constructors, each class has a destructor. Just as a constructor controls initialization, the destructor controls what happens when objects of that class type are destroyed. The destructor for shared_ptr decrements the reference count of the object to which that shared_ptr points. If the count goes to zero, the shared_ptr destructor destroys the object to which the shared_ptr points and frees the memory used by that object. The fact that the shared_ptr class automatically frees dynamic objects when they are no longer needed makes it fairly easy to use dynamic memory. For example, we might have a function that returns a shared_ptr to a dynamically allocated object of a type named Foo that can be initialized by an argument of type T:(note this is just a abstract example, this code can not run in computer now) // factory returns a shared_ptr pointing to a dynamically allocated object shared_ptr<Foo> factory(T arg){ // process arg as appropriate // shared_ptr will take care of deleting this memory return make_shared<Foo>(arg); } Because factory returns a shared_ptr, we can be sure that the object allocated by factory will be freed when appropriate. For example, the following function stores the shared_ptr returned by factory in a local variable: void use_factory(T arg){ shared_ptr<Foo> p = factory(arg); // use p } // p goes out of scope; the memory to which p points is automatically freed This is simple case that show the shared_ptr and a refeence: shared_ptr<string> getS() { auto s = make_shared<string>("hello"); return s; } string& getS(char a) { string s(10, a); return s; } int main(){ // ok output hello auto s1 = getS(); cout << *s1 << endl; // Segmentation fault auto s2 = getS('A'); return 0; } Programs tend to use dynamic memory for one of three purposes: They don’t know how many objects they’ll need They don’t know the precise type of the objects they need They want to share data between several objects The container classes are an example of classes that use dynamic memory for the first purpose. In this section, we’ll define a class that uses dynamic memory in order to let several objects share the same underlying data. So far, the classes we’ve used allocate resources that exist only as long as the corresponding objects. For example, each vector “owns” its own elements. When we copy a vector, the elements in the original vector and in the copy are separate from one another: vector<string> v1; // empty vector { // new scope vector<string> v2 = {"a", "an", "the"}; v1 = v2; // copies the elements from v2 into v1 } // v2 is destroyed, which destroys the elements in v2 // v1 has three elements, which are copies of the ones originally in v2 Some classes allocate resources with a lifetime that is independent of the original object. As an example, assume we want to define a class named Blob that will hold a collection of elements. Unlike the containers, we want Blob objects that are copies of one another to share the same elements. That is, when we copy a Blob, the original and the copy should refer to the same underlying elements. like this: Blob<string> b1; // empty Blob { // new scope Blob<string> b2 = {"a", "an", "the"}; b1 = b2; // b1 and b2 share the same elements } // b2 is destroyed, but the elements in b2 must not be destroyed // b1 points to the elements originally created in b2 Ultimately, we’ll implement our Blob class as a template, but we won’t learn how to do. For now, we’ll define a version of our class that can manage strings. As a result, we’ll name this version of our class StrBlob. The easiest way to implement a new collection type is to use one of the library containers to manage the elements. That way, we can let the library type manage the storage for the elements themselves. In this case, we’ll use a vector to hold our elements. To implement the sharing we want, we’ll give each StrBlob a shared_ptr to a dynamically allocated vector. That shared_ptr member will keep track of how many StrBlobs share the same vector and will delete the vector when the last StrBlob using that vector is destroyed. We still need to decide what operations our class will provide. For now, we’ll implement a small subset of the vector operations. We’ll also change the operations that access elements (e.g., front and back): In our class, these operations will throw an exception if a user attempts to access an element that doesn’t exist. Our class will have a default constructor and a constructor that has a parameter of type initializer_list<string>. This constructor will take a braced list of initializers. This is StrBlob class: #ifndef StrBlob1 #define StrBlob1 #include<memory> #include<vector> #include<string> #include<initializer_list> #include<stdexcept> class StrBlob { public: typedef std::vector<std::string>::size_type size_type; StrBlob() : data(std::make_shared<std::vector<std::string>>()) { }; StrBlob(std::initializer_list<std::string> il) : data(std::make_shared<std::vector<std::string>>(il)) { }; size_type size() const { return data->size(); } bool empty() const { return data->empty(); } // add and remove elements void push_back(const std::string& t) { data->push_back(t); } void pop_back(); // element access std::string& front(); std::string& back(); private: // a shared_ptr ponint to string of vector std::shared_ptr<std::vector<std::string>> data; void check(size_type i, const std::string& msg) const; }; void StrBlob::pop_back() { data->pop_back(); } std::string& StrBlob::front() { // TODO: insert return statement here return data->front(); } std::string& StrBlob::back() { // TODO: insert return statement here return data->back(); } void StrBlob::check(size_type i, const std::string& msg) const { if (i > data->size()) { throw std::out_of_range(msg); } } #endif // !StrBlob This is test code: StrBlob b1; { StrBlob b2 = { "a", "an", "the" }; b1 = b2; b2.push_back("about"); } cout << b1.back() << endl; // output about Managing Memory DirectlyThe language itself defines two operators that allocate and free dynamic memory. The new operator allocates memory, and delete frees memory allocated by new. Objects allocated on the free store are unnamed, so new offers no way to name the objects that it allocates. Instead, new returns a pointer to the object it allocates: int *pi = new int; // pi points to a dynamically allocated, // unnamed, uninitialized int This new expression constructs an object of type int on the free store and returns a pointer to that object. By default, dynamically allocated objects are default initialized, which means that objects of built-in or compound type have undefined value; objects of class type are initialized by their default constructor: string *ps = new string; // initialized to empty string int *pi = new int; // pi points to an uninitialized int We can initialize a dynamically allocated object using direct initialization. We can use traditional construction (using parentheses), and under the new standard, we can also use list initialization (with curly braces): int *pi = new int(1024); // object to which pi points has value 1024 string *ps = new string(10, '9'); // *ps is "9999999999" // vector with ten elements with values from 0 to 9 vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9}; When we provide an initializer inside parentheses, we can use auto to deduce the type of the object we want to allocate from that initializer. However, because the compiler uses the initializer’s type to deduce the type to allocate, we can use auto only with a single initializer inside parentheses: auto p1 = new auto(obj); // p points to an object of the type of obj // that object is initialized from obj auto p2 = new auto{a,b,c}; // error: must use parentheses for the initializer The type of p1 is a pointer to the auto-deduced type of obj. If obj is an int, then p1 is int*; if obj is a string, then p1 is a string*; and so on. The newly allocated object is initialized from the value of obj. Although modern machines tend to have huge memory capacity, it is always possible that the free store will be exhausted. Once a program has used all of its available memory, new expressions will fail. By default, if new is unable to allocate the requested storage, it throws an exception of type bad_alloc. We can prevent new from throwing an exception by using a different form of new: // if allocation fails, new returns a null pointer int *p1 = new int; // if allocation fails, new throws std::bad_alloc int *p2 = new (nothrow) int; // if allocation fails, new returns a null pointer In order to prevent memory exhaustion, we must return dynamically allocated memory to the system once we are finished using it. We return memory through a delete expression. A delete expression takes a pointer to the object we want to free: delete p1; We need notice the pointer we pass to delete must either point to dynamically allocated memory or be a null pointer. Deleting a pointer to memory that was not allocated by new, or deleting the same pointer value more than once, is undefined: auto* p1 = new int(1); int num = 10; int* p2 = &num; delete p1; //ok delete p2; //error When we delete a pointer, that pointer becomes invalid. Although the pointer is invalid, on many machines the pointer continues to hold the address of the (freed) dynamic memory. After the delete, the pointer becomes what is referred to as a dangling pointer. A dangling pointer is one that refers to memory that once held an object but no longer does so. Dangling pointers have all the problems of uninitialized pointers. We can avoid the problems with dangling pointers by deleting the memory associated with a pointer just before the pointer itself goes out of scope. That way there is no chance to use the pointer after the memory associated with the pointer is freed. If we need to keep the pointer around, we can assign nullptr to the pointer after we use delete. Doing so makes it clear that the pointer points to no object. Using shared_ptrs with newAs we’ve seen, if we do not initialize a smart pointer, it is initialized as a null pointer. As described in next table, we can also initialize a smart pointer from a pointer returned by new: member means shared_ptr p(q) p manages the object to which the built-in pointer q points; q must point to memory allocated by new and must be convertible to T*. shared_ptr p(u) p assumes ownership from the unique_ptr u; makes u null. shared_ptr p(q, d) p assumes ownership for the object to which the built-in pointer q points. q must be convertible to T*. p will use the callable object d in place of delete to free q. shared_ptr p(p2, d) p is a copy of the shared_ptr p2 as described in previous Table , except that p uses the callable object d in place of delete. p.reset() If p is the only shared_ptr pointing at its object, reset frees p’s existing object. p.reset(q) If the optional built-in pointer q is passed makes p point to q, otherwise makes p null. p.reset(q, d) If d is supplied, will call d to free q otherwise uses delete to free q. shared_ptr<double> p1; // shared_ptr that can point at a double shared_ptr<int> p2(new int(42)); // p2 points to an int with value 42 The smart pointer constructors that take pointers are explicit. Hence, we cannot implicitly convert a built-in pointer to a smart pointer; we must use the direct form of initialization to initialize a smart pointer: shared_ptr<int> p1 = new int(1024); // error: must use direct initialization shared_ptr<int> p2(new int(1024)); // ok: uses direct initialization By default, a pointer used to initialize a smart pointer must point to dynamic memory because, by default, smart pointers use delete to free the associated object. We can bind smart pointers to pointers to other kinds of resources. However, to do so, we must supply our own operation to use in place of delete. A shared_ptr can coordinate destruction only with other shared_ptrs that are copies of itself. Indeed, this fact is one of the reasons we recommend using make_shared rather than new. That way, we bind a shared_ptr to the object at the same time that we allocate it. There is no way to inadvertently bind the same memory to more than one independently created shared_ptr. Consider the following function that operates on a shared_ptr: // ptr is created and initialized when process is called void process(shared_ptr<int> ptr) { // use ptr } // ptr goes out of scope and is destroyed The parameter to process is passed by value, so the argument to process is copied into ptr. Copying a shared_ptr increments its reference count. Thus, inside process the count is at least 2. When process completes, the reference count of ptr is decremented but cannot go to zero. Therefore, when the local variable ptr is destroyed, the memory to which ptr points will not be deleted. The smart pointer types define a function named get that returns a built-in pointer to the object that the smart pointer is managing. This function is intended for cases when we need to pass a built-in pointer to code that can’t use a smart pointer. The code that uses the return from get must not delete that pointer. shared_ptr<int> p2(new int(10)); auto* p3 = p2.get(); { // undefined: two independent shared_ptrs point to the same memory shared_ptr<int> p4(p3); } // block ends, p4 is destroyed, and the memory to which q points is freed cout << *p2 << endl; // undefined; the memory to which p2 points was freed In this case, both p2 and p4 point to the same memory. Because they were created independently from each other, each has a reference count of 1. When the block in which q was defined ends, q is destroyed. Destroying q frees the memory to which q points. That makes p into a dangling pointer, meaning that what happens when we attempt to use p is undefined. Moreover, when p is destroyed, the pointer to that memory will be deleted a second time. The shared_ptr class gives us a few other operations, which are listed in previous table. We can use reset to assign a new pointer to a shared_ptr: p = new int(1024); // error: cannot assign a pointer to a shared_ptr p.reset(new int(1024)); // ok: p points to a new object Like assignment, reset updates the reference counts and, if appropriate, deletes the object to which p points. The reset member is often used together with unique to control changes to the object shared among several shared_ptrs. Before changing the underlying object, we check whether we’re the only user. If not, we make a new copy before making the change: if (!p.unique()) p.reset(new string(*p)); // we aren't alone; allocate a new copy *p += newVal; // now that we know we're the only pointer, okay to change this object Smart Pointers and ExceptionsIn previous chapter we noted that programs that use exception handling to continue processing after an exception occurs need to ensure that resources are properly freed if an exception occurs. One easy way to make sure resources are freed is to use smart pointers. When we use a smart pointer, the smart pointer class ensures that memory is freed when it is no longer needed even if the block is exited prematurely: void f(){ shared_ptr<int> sp(new int(42)); // allocate a new object // code that throws an exception that is not caught inside f } // shared_ptr freed automatically when the function ends When a function is exited, whether through normal processing or due to an exception, all the local objects are destroyed. In this case, sp is a shared_ptr, so destroying sp checks its reference count. Here, sp is the only pointer to the memory it manages; that memory will be freed as part of destroying sp. In contrast, memory that we manage directly is not automatically freed when an exception occurs. If we use built-in pointers to manage memory and an exception occurs after a new but before the corresponding delete, then that memory won’t be freed: Many C++ classes, including all the library classes, define destructors that take care of cleaning up the resources used by that object. However, not all classes are so well behaved. In particular, classes that are designed to be used by both C and C++ generally require the user to specifically free any resources that are used. unique_ptrA unique_ptr “owns” the object to which it points. Unlike shared_ptr, only one unique_ptr at a time can point to a given object. The object to which a unique_ptr points is destroyed when the unique_ptr is destroyed. Next table lists the operations specific to unique_ptrs. unique_ptr u1 Null unique_ptrs that can point to objects of type T. unique_ptr<T, D> u2 u1 will use delete to free its pointer; u2 will use a callable object of type D to free its pointer. unique_ptr<T, D> u(d) Null unique_ptr that point to objects of type T that uses d, which must be an object of type D in place of delete. u=nullptr Deletes the object to which u points; makes u null. u.release() Relinquishes control of the pointer u had held, returns the pointer u had held and makes u null. u.reset() Deletes the object to which u points u.reset(p) If the built-in pointer q is supplied,makes u point to that object. u.reset(nullptr) Otherwise makes u null. Unlike shared_ptr, there is no library function comparable to make_shared that returns a unique_ptr. Instead, when we define a unique_ptr, we bind it to a pointer returned by new. As with shared_ptrs, we must use the direct form of initialization: unique_ptr<double> p1; // unique_ptr that can point at a double unique_ptr<int> p2(new int(42)); // p2 points to int with value 42 Because a unique_ptr owns the object to which it points, unique_ptr does not support ordinary copy or assignment. Although we can’t copy or assign a unique_ptr, we can transfer ownership from one (nonconst) unique_ptr to another by calling release or reset: // transfers ownership from p1 (which points to the string Stegosaurus) to p2 unique_ptr<string> p2(p1.release()); // release makes p1 null unique_ptr<string> p3(new string("Trex")); // transfers ownership from p3 to p2 p2.reset(p3.release()); // reset deletes the memory to which p2 had pointed There is one exception to the rule that we cannot copy a unique_ptr: We can copy or assign a unique_ptr that is about to be destroyed. The most common example is when we return a unique_ptr from a function: unique_ptr<int> clone(int p) { // ok: explicitly create a unique_ptr<int> from int* return unique_ptr<int>(new int(p)); } weak_ptrA weak_ptr is a smart pointer that does not control the lifetime of the object to which it points. Instead, a weak_ptr points to an object that is managed by a shared_ptr. Binding a weak_ptr to a shared_ptr does not change the reference count of that shared_ptr. Once the last shared_ptr pointing to the object goes away, the object itself will be deleted. That object will be deleted even if there are weak_ptrs pointing to it—hence the name weak_ptr, which captures the idea that a weak_ptr shares its object “weakly.” Dynamic ArraysThe new and delete operators allocate objects one at a time. Some applications, need the ability to allocate storage for many objects at once. For example, vectors and strings store their elements in contiguous memory and must allocate several elements at once whenever the container has to be reallocated. To support such usage, the language and library provide two ways to allocate an array of objects at once. The language defines a second kind of new expression that allocates and initializes an array of objects. The library includes a template class named allocator that lets us separate allocation from initialization. Using an allocator generally provides better performance and more flexible memory management. new and ArraysWe ask new to allocate an array of objects by specifying the number of objects to allocate in a pair of square brackets after a type name. In this case, new allocates the requested number of objects and (assuming the allocation succeeds) returns a pointer to the first one: // call get_size to determine how many ints to allocate int *pia = new int[get_size()]; // pia points to the first of these ints The size inside the brackets must have integral type but need not be a constant. We can also allocate an array by using a type alias to represent an array type. In this case, we omit the brackets: using ten_arr = int[10]; int* p2 = new ten_arr; Although it is common to refer to memory allocated by new ten_arr[] as a “dynamic array,” this usage is somewhat misleading. When we use new to allocate an array, we do not get an object with an array type. Instead, we get a pointer to the element type of the array. Even if we use a type alias to define an array type, new does not allocate an object of array type. Because the allocated memory does not have an array type, we cannot call begin or end on a dynamic array. By default, objects allocated by new—whether allocated as a single object or in an array—are default initialized. We can value initialize the elements in an array by following the size with an empty pair of parentheses. int* pia = new int[10]; // block of ten uninitialized ints int* pia2 = new int[10](); // block of ten ints value initialized to 0 string* psa = new string[10]; // block of ten empty strings string* psa2 = new string[10](); // block of ten empty strings Under the new standard, we can also provide a braced list of element initializers: int* pia = new int[10]{ 1,2,3,4,5,6,7,8,9,0 }; string* psa = new string[10]{ "nvifd",string(10,'A') }; To free a dynamic array, we use a special form of delete that includes an empty pair of square brackets: delete p; // p must point to a dynamically allocated object or be null delete [] pa; // pa must point to a dynamically allocated array or be null The second statement destroys the elements in the array to which pa points and frees the corresponding memory. Elements in an array are destroyed in reverse order. That is, the last element is destroyed first, then the second to last, and so on. The library provides a version of unique_ptr that can manage arrays allocated by new. To use a unique_ptr to manage a dynamic array, we must include a pair of empty brackets after the object type: // up points to an array of ten uninitialized ints unique_ptr<int[]> up(new int[10]); up.release(); // automatically uses delete[] to destroy its pointer unqiue_ptrs that point to arrays provide slightly different operations than those we used in previous, we can use the subscript operator to access the elements in the array: for (size_t i = 0; i < 10; i++) { up[i] = i; } Unlike unique_ptr, shared_ptrs provide no direct support for managing a dynamic array. If we want to use a shared_ptr to manage a dynamic array, we must provide our own deleter: // to use a shared_ptr we must supply a deleter shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; }); sp.reset(); // uses the lambda we supplied that uses delete[] to free the array The fact that shared_ptr does not directly support managing arrays affects how we access the elements in the array: for (size_t i = 0; i < 10; i++) { *(sp.get() + i) = i; } The allocator ClassAn aspect of new that limits its flexibility is that new combines allocating memory with constructing object(s) in that memory. Similarly, delete combines destruction with deallocation. Combining initialization with allocation is usually what we want when we allocate a single object. In that case, we almost certainly know the value the object should have. When we allocate a block of memory, we often plan to construct objects in that memory as needed. In this case, we’d like to decouple memory allocation from object construction. Decoupling construction from allocation means that we can allocate memory in large chunks and pay the overhead of constructing the objects only when we actually need to create them. The library allocator class, which is defined in the memory header, lets us separate allocation from construction. It provides type-aware allocation of raw, unconstructed, memory. Next table outlines the operations that allocator supports. In this section, we’ll describe the allocator operations. methods means allocator a Defines an allocator object named a that can allocate memory for objects of type T. a.allocate(n) Allocates raw, unconstructed memory to hold n objects of type T. a.deallocate(p, n) Deallocates memory that held n objects of type T starting at the address in the T* pointer p. a.construct(p, args) which is used to construct an object in the memory pointed to by p a.destory(p) Runs the destructor on the object pointed to by the T*pointer p. Like vector, allocator is a template. To define an allocator we must specify the type of objects that a particular allocator can allocate. When an allocator object allocates memory, it allocates memory that is appropriately sized and aligned to hold objects of the given type: allocator<string> alloc; // object that can allocate strings auto const p = alloc.allocate(n); // allocate n unconstructed strings The memory an allocator allocates is unconstructed. We use this memory by constructing objects in that memory. In the new library the construct member takes a pointer and zero or more additional arguments: auto q = p; // q will point to one past the last constructed element alloc.construct(q++); // *q is the empty string alloc.construct(q++, 10, 'c'); // *q is cccccccccc alloc.construct(q++, "hi"); // *q is hi! It is an error to use raw memory in which an object has not been constructed: cout << *q << endl; // disaster: q points to unconstructed memory! When we’re finished using the objects, we must destroy the elements we constructed, which we do by calling destroy on each constructed element. The destroy function takes a pointer and runs the destructor on the pointed-to object: while (q != p) alloc.destroy(--q); // free the strings we actually allocated Once the elements have been destroyed, we can either reuse the memory to hold other strings or return the memory to the system. We free the memory by calling deallocate: alloc.deallocate(p, n); The pointer we pass to deallocate cannot be null; it must point to memory allocated by allocate. Moreover, the size argument passed to deallocate must be the same size as used in the call to allocate that obtained the memory to which the pointer points. As a companion to the allocator class, the library also defines two algorithms that can construct objects in uninitialized memory. methods means uninitialized_copy Copy block of memory uninitialized_copy_n Copy block of memory uninitialized_fill Fill block of memory uninitialized_fill_n Fill block of memory As an example, assume we have a vector of ints that we want to copy into dynamic memory. We’ll allocate memory for twice as many ints as are in the vector. We’ll construct the first half of the newly allocated memory by copying elements from the original vector. We’ll construct elements in the second half by filling them with a given value: int main() { vector<int> v(2, 2); allocator<int> alloc; auto p = alloc.allocate(v.size() * 2); auto q = uninitialized_copy(v.begin(), v.end(), p); uninitialized_fill_n(q, v.size(), 20); for (size_t i = 0; i < v.size() * 2; i++) { cout << p[i] << endl; } return 0; } //output //2 //2 //20 //20 Using the Library: A Text-Query ProgramTo conclude our discussion of the library, we’ll implement a simple text-query program. Our program will let a user search a given file for words that might occur in it. The result of a query will be the number of times the word occurs and a list of lines on which that word appears. Design of the Query Program#pragma once #include<fstream> #include<vector> #include<string> #include<set> #include<memory> #include<map> #include<sstream> #include<iostream> // when we write more than one class in a FILE: // we need protected that first declaration class // then implement method by declaration sequential class QueryResult; class TextQuery { public: using line_no = std::vector<std::string>::size_type; TextQuery() = default; TextQuery(std::ifstream& input); QueryResult query(const std::string& s); private: // input file std::shared_ptr<std::vector<std::string>> file; // map of each word to the set of the lines in which that word appears std::map <std::string, std::shared_ptr<std::set<line_no>>> wm; }; class QueryResult { friend std::ostream& print(std::ostream&, const QueryResult&); public: QueryResult() = default; QueryResult(std::string s, std::shared_ptr<std::set<TextQuery::line_no>> p, std::shared_ptr<std::vector<std::string>> f) : s_word(s), lines(p), file(f) { } private: std::string s_word; std::shared_ptr<std::vector<std::string>> file; std::shared_ptr<std::set<TextQuery::line_no>> lines; }; // TextQuery member TextQuery::TextQuery(std::ifstream& input) :file(new std::vector<std::string>) { std::string line, text; while (getline(input, line)) { //read a line and save to vector string file->push_back(line); //get current line_no size_t n = file->size() - 1; //save one word where appeared std::istringstream ss(line); while (ss >> text) { // if word isn't already in wm, subscripting adds a new entry auto& lines = wm[text]; if (!lines) { lines.reset(new std::set<line_no>); } // insert this line number lines->insert(n); } } } QueryResult TextQuery::query(const std::string& s) { // we'll return a pointer to this set if we don't find s static std::shared_ptr<std::set<line_no>> nodata(new std::set<line_no>); auto location = wm.find(s); if (location != wm.end()) { return QueryResult(s, location->second, file); } else { return QueryResult(s, nodata, file); } } // QueryResult member std::ostream& print(std::ostream& out, const QueryResult& qr) { // TODO: insert return statement here out << qr.s_word << " appear " << (qr.lines)->size() << " times:" << std::endl; for (auto& line : *(qr.lines)) { out << line + 1 << ": " << (qr.file)->at(line) << std::endl; } return out; } Main program#include<fstream> #include<iostream> #include "ch12_3.h" using namespace std; int main() { ifstream input("text.txt"); TextQuery tq(input); string s; while (true) { cout << "input a word you want query:" << endl; if (!(cin >> s) || s == "q") { break; } auto qr = tq.query(s); print(std::cout, qr); } return 0; }","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch7) Associative Containers","slug":"Cpp_c07","date":"2021-12-07T13:52:45.000Z","updated":"2023-04-21T15:52:38.835Z","comments":true,"path":"2021/12/07/Cpp_c07/","link":"","permalink":"https://noahbishop.github.io/2021/12/07/Cpp_c07/","excerpt":"","text":"Associative ContainersAssociative and sequential containers differ from one another in a fundamental way: Elements in an associative container are stored and retrieved by a key. In contrast, elements in a sequential container are stored and accessed sequentially by their position in the container. Associative containers support efficient lookup and retrieval by a key. The two primary associative-container types are map and set. The elements in a map are key–value pairs: The key serves as an index into the map, and the value represents the data associated with that index. A set element contains only a key; a set supports efficient queries as to whether a given key is present. We might use a set to hold words that we want to ignore during some kind of text processing. A dictionary would be a good use for a map: The word would be the key, and its definition would be the value. The library provides eight associative containers, listed in next table. These eight differ along three dimensions: Each container is (1) a set or a map, (2) requires unique keys or allows multiple keys, and (3) stores the elements in order or not. Type definition map Associated array; Key-value pairs set unique key; multimap key can appear multiple times multiset key can appear multiple times unordered_map hash ~ unordered_set hash ~ unordered_multimap hash ~ unordered_multiset hash ~ Using an Associative ContainerUsing a mapA classic example that relies on associative arrays is a word-counting program, this program reads its input and reports how often each word appears. //read word from istream, when word is EOF end read int read_word(istream& in, string& word) { in >> word; if (word == ("EOF")) { return 0; } return 1; } int main() { // count the number of times each word occurs in the input map<string, size_t> count_words; string word; while (read_word(cin, word)) { ++count_words[word]; } //in vs2019 type rfor can autofill for range for (const auto& w : count_words) { cout << w.first << " appear " << w.second << " times." << endl; } return 0; } //C++ //java //C++ //c# //EOF //C++ appear 2 times. //c# appear 1 times. //java appear 1 times. Like the sequential containers, the associative containers are templates. To define a map, we must specify both the key and value types. In this program, the map stores elements in which the keys are string and the values are size_t. When we subscript word_count, we use a string as the subscript, and we get back the size_t counter associated with that string. Using a setAs a example, suppose some words we want exclude when count word, we can use a set to save the words need to exclude. //SAME------ //save exclude word while (read_word(cin, word)) { exclude.insert(word); } //count word while (read_word(cin, word)) { if (exclude.find(word) == exclude.end()) ++count_words[word]; } //SAME------ Like the other containers, set is a template. To define a set, we specify the type of its elements, which in this case are string. Overview of the Associative ContainersAssociative containers (both ordered and unordered) support the general container operations covered in previous chapter table. However, the associative containers do not support the sequential-container position-specific operations, such as push_front or back. Defining an Associative ContainerAs we’ve just seen, when we define a map, we must indicate both the key and value type; when we define a set, we specify only a key type, because there is no value type. Each of the associative containers defines a default constructor, which creates an empty container of the specified type. We can also initialize an associative container as a copy of another container of the same type or from a range of values, so long as those values can be converted to the type of the container. Under the new standard, we can also list initialize the elements: map<string, size_t> count_words = { {"hello",3} }; set<string> exclude = { "c++","Java" }; The keys in a map or a set must be unique; there can be only one element with a given key. The multimap and multiset containers have no such restriction; there can be several elements with the same key. For example, the map we used to count words must have only one element per given word. On the other hand, a dictionary could have several definitions associated with a particular word. // define a vector with 20 elements, holding two copies of each number from 0 to 9 vector<int> ivec; for (vector<int>::size_type i = 0; i != 10; ++i) { ivec.push_back(i); ivec.push_back(i); // duplicate copies of each number } // iset holds unique elements from ivec; miset holds all 20 elements set<int> iset(ivec.cbegin(), ivec.cend()); multiset<int> miset(ivec.cbegin(), ivec.cend()); cout << ivec.size() << endl; // prints 20 cout << iset.size() << endl; // prints 10 cout << miset.size() << endl; // prints 20 Requirements on Key TypeThe associative containers place constraints on the type that is used as a key. We’ll cover the requirements for keys in the unordered containers in section 4. For the ordered containers, the key type must define a way to compare the elements. By default, the library uses the < operator for the key type to compare the keys. In the set types, the key is the element type; in the map types, the key is the first type. The pair TypeBefore we look at the operations on associative containers, we need to know about the library type named pair, which is defined in the utility header. A pair holds two data members. Like the containers, pair is a template from which we generate specific types. We must supply two type names when we create a pair. The data members of the pair have the corresponding types. There is no requirement that the two types be the same: pair<string, string> anon; // holds two strings pair<string, size_t> word_count; // holds a string and an size_t pair<string, vector<int>> line; // holds string and vector<int> We can also provide initializers for each member: pair<string, string> author{"James", "Joyce"}; Unlike other library types, the data members of pair are public. These members are named first and second, respectively. cout <<author.first<<author.second << endl; Imagine we have a function that needs to return a pair. Under the new standard we can list initialize the return value: pair<string, int> process(vector<string>& v) { // process v if (!v.empty()) return { v.back(), v.back().size() }; // list initialize else return pair<string, int>(); // explicitly constructed return value } Alternatively, we could have used make_pair to generate a new pair of the appropriate type from its two arguments: return make_pair(v.back(), v.back().size()); Operations on Associative ContainersIn addition to the types listed in previous chapter, the associative containers define the types listed in next list: key_type: type of the key for container tyoe map_type: type associated with each key. value_type: for set same as key_type set<string>::value_type v1; // v1 is a string set<string>::key_type v2; // v2 is a string map<string, int>::value_type v3; // v3 is a pair<const string, int> map<string, int>::key_type v4; // v4 is a string map<string, int>::mapped_type v5; // v5 is an int Associative Container IteratorsWhen we dereference an iterator, we get a reference to a value of the container’s value_type. In the case of map, the value_type is a pair in which first holds the const key and second holds the value: // get an iterator to an element in word_count map<string, size_t> count_words = { {"hello",3} }; auto map_it = count_words.begin(); // *map_it is a reference to a pair<const string, size_t> object cout << map_it->first; // prints the key for this element cout << " " << map_it->second; // prints the value of the element map_it->first = "new key"; // error: key is const ++map_it->second; // ok: we can change the value through an iterator Although the set types define both the iterator and const_iterator types, both types of iterators give us read-only access to the elements in the set. Just as we cannot change the key part of a map element, the keys in a set are also const. We can use a set iterator to read, but not write, an element’s value: set<int> iset = {0,1,2,3,4,5,6,7,8,9}; set<int>::iterator set_it = iset.begin(); if (set_it != iset.end()) { *set_it = 42; // error: keys in a set are read-only cout << *set_it << endl; // ok: can read the key } In general, we do not use the generic algorithms with the associative containers. The fact that the keys are const means that we cannot pass associative container iterators to algorithms that write to or reorder container elements. Such algorithms need to write to the elements. The elements in the set types are const, and those in maps are pairs whose first element is const. Associative containers can be used with the algorithms that read elements. However, many of these algorithms search the sequence. Because elements in an associative container can be found (quickly) by their key, it is almost always a bad idea to use a generic search algorithm. Adding ElementsThe insert members add one element or a range of elements. Because map and set (and the corresponding unordered types) contain unique keys, inserting an element that is already present has no effect: vector<string> k = { "ourb","uvrgsu","ourb" }; auto m = process(k); set<string> s; s.insert(k.begin(), k.end()); s.insert({ "ourb","ivgr" }); The versions of insert that take a pair of iterators or an initializer list work similarly to the corresponding constructors—only the first element with a given key is inserted. When we insert into a map, we must remember that the element type is a pair. Often, we don’t have a pair object that we want to insert. Instead, we create a pair in the argument list to insert: // four ways to add word to word_count word_count.insert({word, 1}); word_count.insert(make_pair(word, 1)); word_count.insert(pair<string, size_t>(word, 1)); word_count.insert(map<string, size_t>::value_type(word, 1)); The value returned by insert (or emplace) depends on the container type and the parameters. For the containers that have unique keys, the versions of insert and emplace that add a single element return a pair that lets us know whether the insertion happened. The first member of the pair is an iterator to the element with the given key; the second is a bool indicating whether that element was inserted, or was already there. If the key is already in the container, then insert does nothing, and the bool portion of the return value is false. If the key isn’t present, then the element is inserted and the bool is true. while (read(cin,word)) { auto isSuccess = count_words.insert({ word,1 }); if (!isSuccess.second) { ++isSuccess.first->second; } } For each word, we attempt to insert it with a value 1. If word is already in the map, then nothing happens. In particular, the counter associated with word is unchanged. If word is not already in the map, then that string is added to the map and its counter value is set to 1. Our word-counting program depends on the fact that a given key can occur only once. That way, there is only one counter associated with any given word. Sometimes, we want to be able to add additional elements with the same key. multimap<string, int> au; au.insert({ "A",10 }); au.insert({ "A",20 }); cout << au.size() << endl; // 2 Erasing ElementsThe associative containers define three versions of erase, which are described in next table. As with the sequential containers, we can erase one element or a range of elements by passing erase an iterator or an iterator pair. These versions of erase are similar to the corresponding operations on sequential containers: The indicated element(s) are removed and the function returns void. version means erase (const_iterator position); erase by a iterator. erase (const key_type& k); erase by a key. erase (first, last) erase by a range of element by a pair of iterator. The version need parameter k removes all the elements, if any, with the given key and returns a count of how many elements were removed. if (word_count.erase(removal_word)) cout << "ok: " << removal_word << " removed\\n"; else cout << "oops: " << removal_word << " not found!\\n"; Subscripting a mapThe map and unordered_map containers provide the subscript operator and a corresponding a~t function. The set types do not support subscripting because there is no “value” associated with a key in a set. The elements are themselves keys, so the operation of “fetching the value associated with a key” is meaningless. We can not subscript a multimap or an unordered_multimap because there may be more than one value associated with a given key. Like the other subscript operators we’ve used, the map subscript takes an index (that is, a key) and fetches the value associated with that key. However, unlike other subscript operators, if the key is not already present, a new element is created and inserted into the map for that key. // a empty map map<string, string> au; // insert a value-initialized element with key a; then assign 10 to its value au["a"] = "10"; // a invalid kay will return initialize value and save cout << au["b"] << endl; au is searched for the element whose key is a. The element is not found. A new key-value pair is inserted into au. The key is a const string holding Anna. The value is value initialized, meaning in this case that the value is "". The newly inserted element is fetched and is given the value “10“. The fact that the subscript operator adds an element if it is not already in the map, On the other hand, sometimes we only want to know whether an element is present and do not want to add the element if it is not. Accessing ElementsThe associative containers provide various ways to find a given element, which are described in next table. Which operation to use depends on what problem we are trying to solve. If all we care about is whether a particular element is in the container, it is probably best to use find. For the containers that can hold only unique keys, it probably doesn’t matter whether we use find or count. However, for the containers with multiple keys, count has to do more work: If the element is present, it still has to count how many elements have the same key. If we don’t need the count, it’s best to use find: methods means find Get iterator to element count Count elements with a specific key lower_bound Return iterator to lower bound upper_bound Return iterator to upper bound equal_range Get range of equal elements multiset<int> iset = { 0,1,2,3,4,5,6,7,8,9,1 }; auto fa1 = iset.find(1); //returns an iterator that refers to the element with key = 1 auto fa2 = iset.find(11); // returns the iterator == iset.end() auto fa3 = iset.count(1); // returns 2 auto fa4 = iset.count(11); // returns 0 Finding an element in an associative container that requires unique keys is a simple matter—the element is or is not in the container. For the containers that allow multiple keys, the process is more complicated: There may be many elements with the given key. When a multimap or multiset has multiple elements of a given key, those elements will be adjacent within the container. multimap<int, int> ikdn; ikdn.insert({ 1,2 }); ikdn.insert({ 2,5 }); ikdn.insert({ 1,7 }); auto num = ikdn.count(1); auto iter = ikdn.find(1); // a iterator point to a pair while (num) { cout << iter->second << endl; ++iter; --num; } We are guaranteed that iterating across a multimap or multiset returns all the elements with a given key in sequence. Alternatively, we can solve our problem using lower_bound and upper_bound. Each of these operations take a key and returns an iterator. If the key is in the container, the iterator returned from lower_bound will refer to the first instance of that key and the iterator returned by upper_bound will refer just after the last instance of the key. If the element is not in the multimap, then lower_bound and upper_bound will return equal iterators; both will refer to the point at which the key can be inserted without disrupting the order. Thus, calling lower_bound and upper_bound on the same key yields an iterator range that denotes all the elements with that key. multimap<int, int> ikdn; ikdn.insert({ 1,2 }); ikdn.insert({ 2,5 }); ikdn.insert({ 1,7 }); for (auto i = ikdn.lower_bound(1); i != ikdn.upper_bound(1); i++) { cout << i->second << endl; } The remaining way to solve this problem is the most direct of the three approaches: Instead of calling upper_bound and lower_bound, we can call equal_range. This function takes a key and returns a pair of iterators. If the key is present, then the first iterator refers to the first instance of the key and the second iterator refers one past the last instance of the key. for (auto pos = ikdn.equal_range(1); pos.first != pos.second; ++pos.first) { cout << pos.first->second << endl; } A Word Transformation Map#include<bits/stdc++.h> using namespace std; void transform(ifstream& in_que, ifstream& in_tran) { map<string, string> dir; //read transform rules string sou, con, text, word; while (in_tran >> sou >> con) { dir[sou] = con; } in_tran.close(); //read question while (getline(in_que, text)) { istringstream ss(text); while (ss >> word) { if (dir.find(word) != dir.end()) { cout << dir[word] << " "; } else { cout << word << " "; } } cout << endl; } } int main() { ifstream in_que("ques.txt"); ifstream in_tran("trans.txt"); transform(in_que, in_tran); return 0; } The Unordered ContainersThe new standard defines four unordered associative containers. Rather than using a comparison operation to organize their elements, these containers use a hash function and the key type’s == operator. An unordered container is most useful when we have a key type for which there is no obvious ordering relationship among the elements. These containers are also useful for applications in which the cost of maintaining the elements in order is prohibitive. Using an Unordered ContainerAside from operations that manage the hashing, the unordered containers provide the same operations (find, insert, and so on) as the ordered containers. That means that the operations we’ve used on map and set apply to unordered_map and unordered_set as well. Similarly for the unordered versions of the containers that allow multiple keys. As a result, we can usually use an unordered container in place of the corresponding ordered container, and vice versa. However, because the elements are not stored in order, the output of a program that uses an unordered container will (ordinarily) differ from the same program using an ordered container. For example, we can rewrite our original word-counting program to use an unordered_map and compare them; #include<bits/stdc++.h> using namespace std; //uppercase string void upperCase(string& str) { for (auto& i : str) { i = toupper(i); } } //read word from istream, when word is EOF end read int read_word(istream& in, string& word) { in >> word; upperCase(word); if (word == ("EOF")) { return 0; } return 1; } int main() { // count the number of times each word occurs in the input map<string, size_t> count_words; unordered_map<string, size_t> unorder_count_words; set<string> exclude = { "c++","Java" }; string word; while (read_word(cin, word)) { // add to order map auto isSuccess1 = count_words.insert({ word,1 }); if (!isSuccess1.second) { ++isSuccess1.first->second; } // add to unorder map auto isSuccess2 = unorder_count_words.insert({ word,1 }); if (!isSuccess2.second) { ++isSuccess2.first->second; } } //order map for (const auto& w : count_words) { cout << w.first << " appear " << w.second << " times." << endl; } cout << "__________" << endl; //unorder map for (const auto& w : unorder_count_words) { cout << w.first << " appear " << w.second << " times." << endl; } return 0; } //c //a //b //eof //A appear 1 times. //B appear 1 times. //C appear 1 times. //__________ //B appear 1 times. //C appear 1 times. //A appear 1 times. The unordered containers are organized as a collection of buckets, each of which holds zero or more elements. These containers use a hash function to map elements to buckets. To access an element, the container first computes the element’s hash code, which tells which bucket to search. The container puts all of its elements with a given hash value into the same bucket. If the container allows multiple elements with a given key, all the elements with the same key will be in the same bucket. As a result, the performance of an unordered container depends on the quality of its hash function and on the number and size of its buckets. The unordered containers provide a set of functions, in here, that let us manage the buckets. These members let us inquire about the state of the container and force the container to reorganize itself as needed. Requirements on Key Type for Unordered ContainersBy default, the unordered containers use the == operator on the key type to compare elements. They also use an object of type hash<key_type> to generate the hash code for each element. The library supplies versions of the hash template for the built in types, including pointers. It also defines hash for some of the library types, including strings and the smart pointer types that we will describe in next chapter. Thus, we can directly define unordered containers whose key is one of the built-in types (including pointer types), or a string, or a smart pointer. However, we cannot directly define an unordered container that uses a our own class types for its key type. Unlike the containers, we cannot use the hash template directly. Instead, we must supply our own version of the hash template.","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch6) Generic Algorithms","slug":"Cpp_c06","date":"2021-12-06T13:52:45.000Z","updated":"2023-04-21T15:52:35.498Z","comments":true,"path":"2021/12/06/Cpp_c06/","link":"","permalink":"https://noahbishop.github.io/2021/12/06/Cpp_c06/","excerpt":"","text":"Generic AlgorithmsThe library containers define a surprisingly small set of operations. Rather than adding lots of functionality to each container, the library provides a set of algorithms, most of which are independent of any particular container type. These algorithms are generic: They operate on different types of containers and on elements of various types. OverviewMost of the algorithms are defined in the algorithm header. The library also defines a set of generic numeric algorithms that are defined in the numeric header. In general, the algorithms do not work directly on a container. Instead, they operate by traversing a range of elements bounded by two iterators. If we want find a substring in string container, we can use the s.find function, however the vector does not provide method find, how could we do? The generic algorithm provides a function called find can do this: int main() { vector<int> k = { 5,4,3,2,1 }; // result will denote the element we want if it's in vec, or vec.cend() if not auto pos1 = find(k.cbegin(), k.cend(), 6); auto pos2 = find(k.cbegin(), k.cend(), 2); cout << (pos1 == k.cend()) << " " << *pos2 << endl; return 0; } //out put //1 2 The first two arguments to find are iterators denoting a range of elements, and the third argument is a value. find compares each element in the given range to the given value. It returns an iterator to the first element that is equal to that value. If there is no match, find returns its second iterator to indicate failure.(in this case id k.cend()) How the Algorithms WorkConceptually, we can list the steps find must take: It accesses the first element in the sequence. It compares that element to the value we want. If this element matches the one we want, find returns a value that identifies this element. Otherwise, find advances to the next element and repeats steps 2 and 3. find must stop when it has reached the end of the sequence. If find gets to the end of the sequence, it needs to return a value indicating that the element was not found. This value and the one returned from step 3 must have compatible types. None of these operations depends on the type of the container that holds the elements. Iterators Make the Algorithms Container IndependentAll but the second step in the find function can be handled by iterator operations: The iterator dereference operator gives access to an element’s value; But Algorithms Do Depend on Element-Type OperationsFor example, step 2, uses the element type’s == operator to compare each element to the given value. A First Look at the AlgorithmsThe library provides more than 100 algorithms. Fortunately, like the containers, the algorithms have a consistent architecture. Understanding this architecture makes learning and using the algorithms easier than memorizing all 100+ of them. With only a few exceptions, the algorithms operate over a range of elements. We’ll refer to this range as the “input range”. The algorithms that take an input range always use their first two parameters to denote that range. These parameters are iterators denoting the first and one past the last elements to process. Read-Only AlgorithmsA number of the algorithms read, but never write to, the elements in their input range. The find function is one such algorithm, as is the count function which count a value how often. Another read-only algorithm is accumulate, which is defined in the numeric header. The first two parameters specify a range of elements to sum. The third is an initial value for the sum. int main() { vector<int> k = { 5,4,5,2,1 }; // output is 2 cout << count(k.cbegin(), k.cend(), 5) << endl; // output is initvalue 2 add all vector equal to 2+17=19 cout << accumulate(k.cbegin(), k.cend(), 2) << endl; return 0; } The type of the third argument to accumulate determines which addition operator is used and is the type that accumulate returns. The fact that accumulate uses its third argument as the starting point for the summation has an important implication: It must be possible to add the element type to the type of the sum. That is, the elements in the sequence must match or be convertible to the type of the third argument. Another read-only algorithm is equal, which lets us determine whether two sequences hold the same values. It compares each element from the first sequence to the corresponding element in the second. It returns true if the corresponding elements are equal, false otherwise. The algorithm takes three iterators: The first two (as usual) denote the range of elements in the first sequence; the third denotes the first element in the second sequence: int main() { vector<int> v1 = { 1,2,3,4,5 }; vector<int> v2 = { 1,2,3,4,5 }; // ouput is 1 cout << equal(v1.cbegin() - 1, v1.cend(), v2.cbegin() - 1) << endl; return 0; } Because equal operates in terms of iterators, we can call equal to compare elements in containers of different types. Moreover, the element types also need not be the same so long as we can use == to compare the element types. int main() { vector<string> vs = { "123","234" }; list<char*> lc = { "123","234" }; // out put is 1 cout << equal(vs.cbegin(), vs.cend(), lc.cbegin()) << endl; return 0; } NOTICE: Algorithms that take a single iterator denoting a second sequence assume that the second sequence is at least as large at the first. Algorithms That Write Container ElementsSome algorithms assign new values to the elements in a sequence. When we use an algorithm that assigns to elements, we must take care to ensure that the sequence into which the algorithm writes is at least as large as the number of elements we ask the algorithm to write. Remember, algorithms do not perform container operations, so they have no way themselves to change the size of a container. the fill algorithm takes a pair of iterators that denote a range and a third argument that is a value. fill assigns the given value to each element in the input sequence: int main() { vector<string> vs(10); fill(vs.begin(), vs.end(), "A"); return 0; } Some algorithms take an iterator that denotes a separate destination. These algorithms assign new values to the elements of a sequence starting at the element denoted by the destination iterator. For example, the fill_n function takes a single iterator, a count, and a value. It assigns the given value to the specified number of elements starting at the element denoted to by the iterator. int main() { vector<string> vs(10); fill_n(vs.begin(), vs.size(), "A"); return 0; } One way to ensure that an algorithm has enough elements to hold the output is to use an insert iterator. An insert iterator is an iterator that adds elements to a container. back_inserter, which is a function defined in the iterator header. back_inserter takes a reference to a container and returns an insert iterator bound to that container. When we assign through that iterator, the assignment calls push_back to add an element with the given value to the container: int main() { vector<string> k; auto it = back_inserter(k); *it = "hello"; cout << *k.cbegin() << endl; return 0; } // output // hello We frequently use back_inserter to create an iterator to use as the destination of an algorithm. For example: int main() { vector<string> k; fill_n(back_inserter(k), 5, "A"); for (auto m : k) { cout << m; } cout << endl; return 0; } // output // AAAAA The copy algorithm is another example of an algorithm that writes to the elements of an output sequence denoted by a destination iterator. This algorithm takes three iterators. The first two denote an input range; the third denotes the beginning of the destination sequence. The value returned by copy is the (incremented) value of its destination iterator. That is, ret will point just past the last element copied into a2. int main() { int a1[] = { 0,1,2,3,4,5,6,7,8,9 }; int a2[sizeof(a1) / sizeof(*a1)]; // a2 has the same size as a1 // ret points just past the last element copied into a2 auto ret = copy(begin(a1), end(a1), a2); // copy a1 into a2 cout << *(ret - 1) << endl; // output is 9 return 0; } the replace algorithm reads a sequence and replaces every instance of a given value with another value. This algorithm takes four parameters: two iterators denoting the input range, and two values. It replaces each element that is equal to the first value with the second: int main() { vector<string> k; fill_n(back_inserter(k), 5, "A"); replace(k.begin(), k.end(), "A", "B"); //output BBBBB for (auto m : k) { cout << m; } cout << endl; return 0; } If we want to leave the original sequence unchanged, we can call replace_copy. That algorithm takes a third iterator argument denoting a destination in which to write the adjusted sequence: replace_copy(k.begin(), k.end(), back_inserter(m), "A", "B"); Algorithms That Reorder Container ElementsSome algorithms rearrange the order of elements within a container. An obvious example of such an algorithm is sort. A call to sort arranges the elements in the input range into sorted order using the element type’s < operator. int main() { vector<int> vec = { 5,4,3,4,1,2 }; sort(vec.begin(), vec.end()); return 0; } To eliminate the duplicated words, we will first sort the vector so that duplicated words appear adjacent to each other. Once the vector is sorted, we can use another library algorithm, named unique, to reorder the vector so that the unique elements appear in the first part of the vector. Because algorithms cannot do container operations, we’ll use the erase member of vector to actually remove the elements: void elimDups(vector<int>& vec) { sort(vec.begin(), vec.end()); auto end_unique = unique(vec.begin(), vec.end()); vec.erase(end_unique, vec.end()); } You can see more details about unique in here. Shortly, the removal is done by replacing the duplicate elements by the next element that is not a duplicate, and signaling the new size of the shortened range by returning an iterator to the element that should be considered its new past-the-end element. #include<bits/stdc++.h> using namespace std; void printVec(vector<int>& vec) { for (auto veci : vec) { cout << veci << " "; } cout << endl; } void elimDups(vector<int>& vec) { // sorted sort(vec.begin(), vec.end()); printVec(vec); // uniqued auto end_unique = unique(vec.begin(), vec.end()); printVec(vec); // remove duplicates vec.erase(end_unique, vec.end()); printVec(vec); } int main() { vector<int> vec = { 5,4,3,4,1,2,1 }; elimDups(vec); return 0; } the end_unique denotes one past the last unique element. Customizing OperationsMany of the algorithms compare elements in the input sequence. By default, such algorithms use either the element type’s < or == operator. The library also defines versions of these algorithms that let us supply our own operation to use in place of the default operator. For example, the sort algorithm uses the element type’s < operator. However, we might want to sort a sequence into a different order from that defined by <, or our sequence might have elements of a type (such as Sales_data) that does not have a < operator. In both cases, we need to override the default behavior of sort. Passing a Function to an AlgorithmAs one example, assume that we want to sort string order by length, and then alphabetically within each size. To reorder the vector by length, we’ll use a second, overloaded version of sort. This version of sort takes a third argument that is a predicate. A predicate is an expression that can be called and that returns a value that can be used as a condition. The predicates used by library algorithms are either unary predicates (meaning they have a single parameter) or binary predicates (meaning they have two parameters). #include<bits/stdc++.h> using namespace std; bool isShort(const string& str1, const string& str2) { if (str1.size() != str2.size()) { return str1.size() < str2.size(); } else { return str1 < str2; } } int main() { vector<string> vec = { "bbcd","abcd","c" }; sort(vec.begin(), vec.end(), isShort); for (auto i = vec.begin(); i != vec.end(); i++) { cout << *i << " "; } cout << endl; return 0; } Lambda ExpressionsThe predicates we pass to an algorithm must have exactly one or two parameters, depending on whether the algorithm takes a unary or binary predicate, respectively. However, sometimes we want to do processing that requires more arguments than the algorithm’s predicate allows. As a example, if we want find the first element in the string vector that has the given size, we can use the library find_if algorithm to find an element that has a particular size. Like find, the find_if algorithm takes a pair of iterators denoting a range. Unlike find, the third argument to find_if is a predicate. The find_if algorithm calls the given predicate on each element in the input range. It returns the first element for which the predicate returns a nonzero value, or its end iterator if no such element is found. However, find_if takes a unary predicate—any function we pass to find_if must have exactly one parameter that can be called with an element from the input sequence. There is no way to pass a second argument representing the size. We can pass any kind of callable object to an algorithm. The only callables we’ve used so far are functions and function pointers. There are two other kinds of callables: classes that overload the function-calloperator, and lambda expressions. The format of lambda expressions is [capture list] (parameter list) -> return type { function body }. capture list is an (often empty) list of local variables defined in the enclosing function return type, parameter list, and function body are the same as in any ordinary function. We’re now ready to solve our original problem, which is to write a callable expression that we can pass to find_if. We want an expression that will compare the length of each string in the input sequence with the value of the sz parameter in the biggies function. int main() { vector<string> vec = { "bbcd","abcd","sd","c" }; size_t sz = 2; auto wc = find_if(vec.begin(), vec.end(), [sz](const string& str) { return str.size() <= sz; }); cout << *wc << endl; // output is sd return 0; } Lambda Captures and ReturnsWhen we define a lambda, the compiler generates a new (unnamed) class type that corresponds to that lambda. For now, what’s useful to understand is that when we pass a lambda to a function, we are defining both a new type and an object of that type: The argument is an unnamed object of this compiler-generated class type. Similarly, when we use auto to define a variable initialized by a lambda, we are defining an object of the type generated from that lambda. Similar to parameter passing, we can capture variables by value or by reference. As with a parameter passed by value, it must be possible to copy such variables. Unlike parameters, the value of a captured variable is copied when the lambda is created, not when it is called: int v = 10; auto k = [v]() {return v; }; v = 100; cout << k() << endl; // output is 10 We can also define lambdas that capture variables by reference. For example: int v = 10; auto k = [v]() {return v; }; v = 100; cout << k() << endl; // output is 10 Rather than explicitly listing the variables we want to use from the enclosing function, we can let the compiler infer which variables we use from the code in the lambda’s body. To direct the compiler to infer the capture list, we use an & or = in the capture list. The & tells the compiler to capture by reference, and the = says the values are captured by value. int v = 10; auto k = [=]() {return v; }; v = 100; cout << k() << endl; // output is 10 int v = 10; auto k = [&]() {return v; }; v = 100; cout << k() << endl; // output is 100 If we want to capture some variables by value and others by reference, we can mix implicit and explicit captures: int v = 10, d = 20; auto k = [=, &d]() {return v + d; }; v = 100; d = 50; cout << k() << endl; // output is 60 By default, a lambda may not change the value of a variable that it copies by value. If we want to be able to change the value of a captured variable, we must follow the parameter list with the keyword mutable. Lambdas that are mutable may not omit the parameter list: int v = 10; auto k = [v]()mutable {v = 100; return v; }; cout << k() << endl; // output is 100 cout << v << endl; // output is 10 The lambdas we’ve written so far contain only a single return statement. We can use -> type specify return type. auto k = [=, &d]()->int {return v + d; }; Binding ArgumentsIt is usually straightforward to use a function in place of a lambda that has an empty capture list. As we’ve seen, we can use either a lambda or our isShort function to order the vector on word length. However, it is not so easy to write a function to replace a lambda that captures local variables. For example, the lambda that we used in the call to find_if compared a string with a given size. We can easily write a function to do the same work: bool check_size(const string &s, string::size_type sz){ return s.size() >= sz; } However, we can’t use this function as an argument to find_if. Because find_if takes a unary predicate. The lambda that biggies passed to find_if used its capture list to store sz. In order to use check_size in place of that lambda, we have to figure out how to pass an argument to the sz parameter. We can solve the problem of passing a size argument to check_size by using a new library function named bind, which is defined in the functional header. The bind function can be thought of as a general-purpose function adaptor. It takes a callable object and generates a new callable that “adapts” the parameter list of the original object. The form is auto newCallable = bind(callable, arg_list);. The arguments in arg_list may include names of the form placeholders::_n, where n is an integer. These arguments are “placeholders” representing the parameters of newCallable. They stand “in place of” the arguments that will be passed to newCallable. The number n is the position of the parameter in the generated callable: _1 is the first parameter in newCallable, _2 is the second, and so forth. auto check_new = bind(check_size, placeholders::_1, 2); Using bind, we can replace our original lambda-based call to find_if: auto wc = find_if(vec.begin(), vec.end(), check_new); Revisiting IteratorsIn addition to the iterators that are defined for each of the containers, the library defines several additional kinds of iterators in the iterator header. These iterators include Insert iterators: These iterators are bound to a container and can be used to insert elements into the container. Stream iterators: These iterators are bound to input or output streams and can be used to iterate through the associated IO stream. Reverse iterators: These iterators move backward, rather than forward. The library containers, other than forward_list, have reverse iterators. Move iterators: These special-purpose iterators move rather than copy their elements. Insert IteratorsAn inserter is an iterator adaptor that takes a container and yields an iterator that adds elements to the specified container. There are three kinds of inserters. Each differs from the others as to where elementsare inserted: back_inserter creates an iterator that uses push_back. front_inserter creates an iterator that uses push_front. inserter creates an iterator that uses insert. This function takes a second argument, which must be an iterator into the given container. Elements are inserted ahead of the element denoted by the given iterator. It is important to understand that when we call inserter(c, iter), we get an iterator that, when used successively, inserts elements ahead of the element originally denoted by iter. That is, if it is an iterator generated by inserter, then an assignment such as *it = va1; behaves as: it = c.insert(it, val); // it points to the newly added element ++it; // increment it so that it denotes the same element as before iostream IteratorsEven though the iostream types are not containers, there are iterators that can be used with objects of the IO types. An istream_iterator reads an input stream, and an ostream_iterator writes an output stream. This part as a expand you can read chapter10 section 4 in c++ primer 5th. Structure of Generic AlgorithmsThe most fundamental property of any algorithm is the list of operations it requires from its iterator(s). Some algorithms, such as find, require only the ability to access an element through the iterator, to increment the iterator, and to compare two iterators for equality. Others, such as sort, require the ability to read, write, and randomly access elements. The iterator operations required by the algorithms are grouped into five iterator categories listed in next table. Each algorithm specifies what kind of iterator must be supplied for each of its iterator parameters. Type Characteristics Input iterator Read, Increment only Output iterator Write, Increment only Forward iterator Read and write, Increment only Bidirectional iterator Read and write, Increment and Descrement Random_access iterator Read and write, full iterator arithmetic The Iterator Categories Input iterators: can read elements in a sequence. An input iterator must provide Equality and inequality operators (==, !=) to compare two iterators Prefix and postfix increment (++) to advance the iterator Dereference operator (*) to read an element; dereference may appear only on the right-hand side of an assignment The arrow operator (->) as a synonym for (* it).member—that is, dereference the iterator and fetch a member from the underlying object. Output iterators: can be thought of as having complementary functionality to input iterators; they write rather than read elements. Output iterators must provide Prefix and postfix increment (++) to advance the iterator Dereference (*), which may appear only as the left-hand side of an assignment (Assigning to a dereferenced output iterator writes to the underlying element.) Forward iterators: can read and write a given sequence. They move in only one direction through the sequence. Forward iterators support all the operations of both input iterators and output iterators. Bidirectional iterators: can read and write a sequence forward or backward. In addition to supporting all the operations of a forward iterator, a bidirectional iterator also supports the prefix and postfix decrement (--) operators. Random-access iterators: provide constant-time access to any position in the sequence. These iterators support all the functionality of bidirectional iterators. Container-Specific AlgorithmsUnlike the other containers, list and forward_list define several algorithms as members. In particular, the list types define their own versions of sort, merge, remove, reverse, and unique. you can see details in here.","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch5) Sequential Containers","slug":"Cpp_c05","date":"2021-12-05T13:52:45.000Z","updated":"2023-04-21T15:52:32.560Z","comments":true,"path":"2021/12/05/Cpp_c05/","link":"","permalink":"https://noahbishop.github.io/2021/12/05/Cpp_c05/","excerpt":"","text":"Sequential ContainersOverview of the Sequential Containersexcept vector, string and array, there are some new sequential containers: deque, list and forward_list. Their characteristics show in next table: Type Characteristics deque Double-end queue, support random access list Doubly linked list, only support sequential access forward_list Singly linked list, only support sequential access With the exception of array, which is a fixed-size container, the containers provide efficient, flexible memory management. We can add and remove elements, growing and shrinking the size of the container. string and vector hold their elements in contiguous memory. Because elements are contiguous, it is fast to compute the address of an element from its index. However, adding or removing elements in the middle of one of these containers need take more time. The list and forward_list containers are designed to make it fast to add or remove an element anywhere in the container. In exchange, these types do not support random access to elements. A deque is a more complicated data structure. Like string and vector, deque supports fast random access and adding or removing elements in the middle of a deque is a (potentially) expensive operation. However, adding or removing elements at either end of the deque is a fast operation, comparable to adding an element to a list or forward_list. Container Library OverviewThe operations on the container types form a kind of hierarchy, this table show the type of container: member type definition value_type The first template parameter (T) allocator_type The second template parameter (Alloc) reference value_type& pointer allocator_traits<allocator_type>::pointer iterator a random access iterator to value_type const_(reference\\ pointer\\ iterator) ~ difference_type a signed integral type, identical to distance between two iterator size_type an unsigned integral type that can represent any non-negative value of difference_type This table show the normal operation: method definition begin Return iterator to beginning end Return iterator to end cbegin Return const iterator to beginning cend Return const iterator to end swap swap elements in two containers size the number of elements in container max_size the max number of elements container can hold capacity Return size of allocated storage capacity The containers are class templates. As with vectors, we must supply additional information to generate a particular container type. Almost any type can be used as the element type of a sequential container. In particular, we can define a container whose element type is itself another container. We define such containers exactly as we do any other container type: We specify the element type (which in this case is a container type) inside angle brackets: vector<vector<string>> lines; // vector of vectors vector<vector<string> >; //Older compilers may require a space between the angle brackets IteratorsAn iterator range is denoted by a pair of iterators each of which refers to an element, or to one past the last element, in the same container. These two iterators, often referred to as begin and end—or (somewhat misleadingly) as first and last—mark a range of elements from the container. The name last, although commonly used, is a bit misleading, because the second iterator never refers to the last element of the range. Instead, it refers to a point one past the last element. This element range is called a left-inclusive interval. The standard mathematical notation for such a range is [begin, end). begin and end MembersThe begin and end operations yield iterators that refer to the first and one past the last element in the container. These iterators are most often used to form an iterator range that encompasses all the elements in the container. The versions with an r return reverse iterators. Those that start with a c return the const version of the related iterator: list<string> a = {"Milton", "Shakespeare", "Austen"}; auto it1 = a.begin(); // list<string>::iterator auto it2 = a.rbegin(); // list<string>::reverse_iterator auto it3 = a.cbegin(); // list<string>::const_iterator auto it4 = a.crbegin();// list<string>::const_reverse_iterator Assignment and swapThe assignment-related operators act on the entire container. The assignment operator replaces the entire range of elements in the left hand container with copies of the elements from the right-hand operand: int main() { vector<int> k(10, 20); vector<int> m(5, 40); k = m; for (auto i = k.begin(); i != k.cend(); i++) { cout << *i << " "; } return 0; } //output 40 40 40 40 40 The assignment operator requires that the left-hand and right-hand operands have the same type. It copies all the elements from the right-hand operand into the left-hand operand. The sequential containers (except array) also define a member named assign that lets us assign from a different but compatible type, or assign from a subsequence of a container. int main() { vector<char> k(10, 'A'); vector<int> m(5, 40); k.assign(m.begin(), m.end()); for (auto i = k.begin(); i != k.cend(); i++) { cout << *i << ""; } return 0; } //output ( ( ( ( ( A second version of assign takes an integral value and an element value. It replaces the elements in the container with the specified number of elements, each of which has the specified element value: list<string> slist1(1); // one element, which is the empty string slist1.assign(10, "Hiya!"); // ten elements; each one is Hiya ! The swap operation exchanges the contents of two containers of the same type. After the call to swap, the elements in the two containers are interchanged: int main() { vector<int> k(5, 20); vector<int> m(5, 40); k.swap(m); for (auto i = k.begin(); i != k.cend(); i++) { cout << *i << " "; } for (auto i = m.begin(); i != m.cend(); i++) { cout << *i << " "; } return 0; } //output 40 40 40 40 40 20 20 20 20 20 Sequential Container OperationsAdd elementsExcepting array, all of the library containers provide flexible memory management. We can add or remove elements dynamically changing the size of the container at run time. push_back appends an element to the back of a vector. Aside from array and forward_list, every sequential container (including the string type) supports push_back. The call to push_back creates a new element at the end of container, increasing the size of container by 1. The value of that element is a copy of word. The type of container can be any of list, vector, or deque. Because string is just a container of characters, we can use push_back to add characters to the end of the string: int main() { string s1 = "hell"; s1.push_back('o'); // s1 is hello return 0; } In addition to push_back, the list, forward_list, and deque containers support an analogous operation named push_front. This operation inserts a new element at the front of the container: int main() { deque<int> k(1, 5); k.push_back(6); k.push_front(4); return 0; } // k is a deque with elemrnts 4,5,6 More generally, the insert members let us insert zero or more elements at any point in the container. The insert members are supported for vector, deque, list, and string. Each of the insert functions takes an iterator as its first argument. The iterator indicates where in the container to put the element(s). int main() { vector<int> k = { 1,2,4,5 }; auto it = k.begin(); it += 2; k.insert(it, 3); return 0; } //k is a vector with elements 1,2,3,4,5 The arguments to insert that appear after the initial iterator argument are analogous to the container constructors that take the same parameters. The version that takes an element count and a value adds the specified number of identical. int main() { vector<int> k; // k = {1} k.insert(k.end(), 1, 1); // k = {1,2,3} k.insert(k.end(), { 2,3 }); // k = {1,2,3,4,5} vector<int> t = { 4,5 }; k.insert(k.end(), t.begin(), t.end()); return 0; } Under the new standard, the versions of insert that take a count or a range return an iterator to the first element that was inserted. (In prior versions of the library, these operations returned void.) We can use the value returned by insert to repeatedly insert elements at a specified position in the container: int main() { vector<int> k; auto iter = k.begin(); for (int i = 0; i < 5; i++) { iter = k.insert(iter, i); } return 0; } // k = {4,3,2,1,0} The new standard introduced three new members—emplace_front, emplace, and emplace_back—that construct rather than copy elements. These operations correspond to the push_front, insert, and push_back operations in that they let us put an element at the front of the container, in front of a given position, or at the back of the container, respectively. c.emplace_back("978-0590353403", 25, 15.99); c.push_back(Sales_data("978-0590353403", 25, 15.99)); The call to emplace_back and the second call to push_back both create new Sales_data objects. In the call to emplace_back, that object is created directly in space managed by the container. The call to push_back creates a local temporary object that is pushed onto the container. Accessing ElementsEach sequential container, including array, has a front member, and all except forward_list also have a back member. These operations return a reference to the first and last element, respectively: if (!c.empty()) { // val and val2 are copies of the value of the first element in c auto val = *c.begin(), val2 = c.front(); // val3 and val4 are copies of the of the last element in c auto last = c.end(); auto val3 = *(--last); // can't decrement forward_list iterators auto val4 = c.back(); // not supported by forward_list } The containers that provide fast random access (string, vector, deque, and array) also provide the subscript operator []. As we’ve seen, the subscript operator takes an index and returns a reference to the element at that position in the container. The index must be “in range” (i.e., greater than or equal to 0 and less than the size of the container). It is up to the program to ensure that the index is valid; the subscript operator does not check whether the index is in range. Using an out-of-range value for an index is a serious programming error, but one that the compiler will not detect. If we want to ensure that our index is valid, we can use the at member instead. The at member acts like the subscript operator, but if the index is invalid, at throws an out_of_range exception. Erasing ElementsJust as there are several ways to add elements to a (no-array) container there are also several ways to remove elements. The pop_front and pop_back functions remove the first and last elements, respectively. Just as there is no push_front for vector and string, there is also no pop_front for those types. Similarly, forward_list does not have pop_back. Like the element access members, we may not use a pop operation on an empty container. int main() { deque<int> k = { 1,1,2,3,3 }; k.pop_back(); k.pop_front(); return 0; } // k = {1,2,3} The erase members remove element(s) at a specified point in the container. We can delete a single element denoted by an iterator or a range of elements marked by a pair of iterators. Both forms of erase return an iterator referring to the location after the (last) element that was removed. int main() { list<int> lst = { 0,1,2,3,4,5,6,7,8,9 }; auto it = lst.begin(); while (it != lst.end()) if (*it % 2) // if the element is odd it = lst.erase(it); // erase this element else ++it; return 0; } delete a range of elements: int main() { vector<int> lst = { 0,1,2,3,4,5,6,7,8,9 }; auto it_beg = lst.begin(); auto it_end = lst.begin() + 5; lst.erase(it_beg, it_end); return 0; } // lst = {5,6,7,8,9} Resizing a ContainerWith the usual exception of arrays, we can use resize, to make a container larger or smaller. If the current size is greater than the requested size, elements are deleted from the back of the container; if the current size is less than the new size, elements are added to the back of the container: list<int> ilist(10, 42); // ten ints: each has value 42 ilist.resize(15); // adds five elements of value 0 to the back of ilist ilist.resize(25, -1); // adds ten elements of value -1 to the back of ilist ilist.resize(5); // erases 20 elements from the back of ilist How a vector GrowsTo support fast random access, vector elements are stored contiguously—each element is adjacent to the previous element. If there is no room for the new element, the container can’t just add an elementsomewhere else in memory—the elements must be contiguous. Instead, the container must allocate new memory to hold the existing elements plus the new one, move the elements from the old location into the new space, add the new element, and deallocate the old memory. If vector did this memory allocation and deallocation each time we added an element, performance would be unacceptably slow. To avoid these costs, library implementors use allocation strategies that reduce the number of times the container is reallocated. It is important to understand the difference between capacity and size. The size of a container is the number of elements it already holds; its capacity is how many elements it can hold before more space must be allocated. A vector may be reallocated only when the user performs an insert operation when the size equals capacity or by a call to resize or reserve with a value that exceeds the current capacity. How much memory is allocated beyond the specified amount is up to the implementation. int main() { vector<int> ivec(50, 1); cout << ivec.capacity() << endl; ivec.push_back(1); cout << ivec.capacity() << endl; return 0; } // in my computer the output is // 50 // 100 Additional string OperationsSubstring And ReplaceThe substr operation returns a string that is a copy of part or all of the original string. We can pass substr an optional starting position and count: string s("hello world"); string s2 = s.substr(0, 5); // s2 = hello string s3 = s.substr(6); // s3 = world string s4 = s.substr(6, 11); // s3 = world string s5 = s.substr(12); // throws an out_of_range exception The string type supports the sequential container assignment operators and the assign, insert, and erase operations, It also defines additional versions of insert and erase. string provides versions that take an index. The index indicates the starting element to erase or the position before which to insert the given values: string s = "hello"; // insert five exclamation points at the end of s // s = “Hello!!!!!!” s.insert(s.size(), 5, '!'); s.erase(s.size() - 5, 5); // erase the last five characters from s The string class defines two additional members, append and replace, that can change the contents of a string. The append operation is a shorthand way of inserting at the end: int main() { string s("C++ Primer"); // initialize s and s2 to "C++Primer s.append(" 4th Ed."); cout << s << endl; s.replace(11, 3, "5th"); cout << s << endl; return 0; } // output // C++ Primer 4th Ed. // C++ Primer 5th Ed. Find functionThe string class provides six different search functions, each of which has four overloaded versions. The next table describes the search members and their definition. Each of these search operations returns a string::size_type value that is the index of where the match occurred. If there is no match, the function returns a static member (§ 7.6, p. 300) named string::npos. The library defines npos asa const string::size_type initialized with the value -1. Because npos is an unsigned type, this initializer means npos is equal to the largest possible size any string could have. method definition find Find first occurrence of content in string rfind Find last occurrence of content in string find_first_of Find any character match in string find_last_of Find character in string from the end find_first_not_of Find absence of character in string find_last_not_of Find non-matching character in string from the end int main() { string s("AA BB AA DD AA BB AA"); // return the first match position auto pos = s.find("AA"); cout << pos << s.substr(pos) << endl; //return the last match position pos = s.rfind("AA"); cout << pos << s.substr(pos) << endl; // return the first char match position pos = s.find_first_of("DB"); cout << pos << s.substr(pos) << endl; // return the last char match position pos = s.find_last_of("DB"); cout << pos << s.substr(pos) << endl; // return the first char not in metch string pos = s.find_first_not_of("AB "); cout << pos << s.substr(pos) << endl; // return the last char not in metch string pos = s.find_last_not_of("AD "); cout << pos << s.substr(pos) << endl; return 0; } //output //0AA BB AA DD AA BB AA //18AA //3BB AA DD AA BB AA //16B AA //9DD AA BB AA //16B AA We can pass an optional starting position to the find operations. This optional argument indicates the position from which to start the search. By default, that position is set to zero. One common programming pattern uses this optional argument to loop through a string finding all occurrences: string::size_type pos = 0; // each iteration finds the next number in name while ((pos = name.find_first_of(numbers, pos))!= string::npos) { cout << "found number at index: " << pos << " element is " << name[pos] << endl; ++pos; // move to the next character } CompareIn addition to the relational operators, the string library provides a set of compare functions that are similar to the C library strcmp function. The function compare returns zero or a positive or negative value depending on whether s is equal to, greater than, or less than the string formed from the given arguments. int main() { std::string str1("green apple"); std::string str2("red apple"); if (str1.compare(str2) != 0) std::cout << str1 << " is not " << str2 << endl; if (str1.compare(6, 5, "apple") == 0) std::cout << "still, " << str1 << " is an apple" << endl; if (str2.compare(str2.size() - 5, 5, "apple") == 0) std::cout << "and " << str2 << " is also an apple" << endl; if (str1.compare(6, 5, str2, 4, 5) == 0) std::cout << "therefore, both are apples" << endl; return 0; } //output //green apple is not red apple //still, green apple is an apple //and red apple is also an apple //therefore, both are apples previous code show the most common usage, more details you can check this website. Numeric ConversionsStrings often contain characters that represent numbers. In c++ 11 the new standard introduced several functions that convert between numeric data and library strings: int main() { // converts the double i to its character representation // note output the i and s is different double i = 42.97538045793466769837795; string s = to_string(i); cout << i << endl; cout << s << endl; // converts a string to double string k = "1999.9"; double tod = stod(k); // converts a string to int string j = "6745"; int toi = stoi(k); return 0; } the function that string to numeric type can be think prefix sto add a suffix represent the numeric type, stoi means string to int, stof means string to float. etc… Container AdaptorsIn addition to the sequential containers, the library defines three sequential container adaptors: stack, queue, and priority_queue. An adaptor is a general concept in the library. There are container, iterator, and function adaptors. Essentially, an adaptor is a mechanism for making one thing act like another. A container adaptor takes an existing container type and makes it act like a different type. Defining an AdaptorEach adaptor defines two constructors: the default constructor that creates an empty object, and a constructor that takes a container and initializes the adaptor by copying the given container. deque<int> mydeque = { 1,2,3,4,5 }; // empty stack stack<int> myst1; // copies elements from deq into stk stack<int> myst2(mydeque); By default both stack and queue are implemented in terms of deque, and a priority_queue is implemented on a vector. We can override the default container type by naming a sequential container as a second type argument when we create the adaptor: int main() { vector<string> svec = { "1","2","3","4","5" }; // empty stack implemented on top of vector stack<string, vector<string>> str_stk; // str_stk2 is implemented on top of vector and initially holds a copy of svec stack<string, vector<string>> str_stk2(svec); return 0; } A stack requires only push_back, pop_back, and back operations, so we can use any of the remaining container types for a stack. A queue adaptor requires back, push_back, front, and push_front, so it can be built on alist or deque but not on a vector. A priority_queue requires random access in addition to the front, push_back, and pop_back operations; it can be built on a vector or a deque but not on a list. Stack AdaptorThe three most common methods in stack are push, pop, top, the push add a element to the top of stack, the pop remove a element to the top of stack, the top return the top of stack. int main() { stack<int> m; // add 1,2,3,4,5 by sequential for (int i = 0; i < 5; i++) { m.push(i); } // pop elemrnt for (int i = 0; i < 5; i++) { cout << m.top() << " "; m.pop(); } cout << endl; return 0; } //output //4 3 2 1 0 The Queue AdaptorsThe queue and priority_queue adaptors are defined in the queue header. The library queue uses a first-in, first-out (FIFO) storage and retrieval policy. Objects entering the queue are placed in the back and objects leaving the queue are removed from the front. A restaurant that seats people in the order in which they arrive is an example of a FIFO queue. The four most common methods in queue are push, pop, front, back, top push: add a element to end of queue pop: remove a element in the head of queue front: return a element from the head of queue back: return a element in the end of queue (only valid in queue) top: return the most priority element in priority_queue (only valid in priority_queue) A priority_queue lets us establish a priority among the elements held in the queue. Newly added elements are placed ahead of all the elements with a lower priority. A restaurant that seats people according to their reservation time, regardless of when they arrive, is an example of a priority queue. By default, the library uses the < operator on the element type to determine relative priorities. However we can override the default. int main() { queue<int> m; // add 1,2,3,4,5 by sequential for (int i = 0; i < 5; i++) { m.push(i); } // pop elemrnt for (int i = 0; i < 5; i++) { cout << m.front() << " "; m.pop(); } cout << endl; return 0; } //0 1 2 3 4","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch4) The C++ IO Library","slug":"Cpp_c04","date":"2021-12-04T13:52:45.000Z","updated":"2023-04-21T15:52:28.265Z","comments":true,"path":"2021/12/04/Cpp_c04/","link":"","permalink":"https://noahbishop.github.io/2021/12/04/Cpp_c04/","excerpt":"","text":"The IO LibraryThe IO ClassesThe IO types and objects that we have used so far manipulate char data. By default these objects are connected to the user’s console window. Of course, real programs cannot be limited to doing IO solely to or from a console window. Programs often need to read or write named files. Moreover, it can be convenient to use IO operations to process the characters in a string. Applications also may have to read and write languages that require wide-character support. To support these different kinds of IO processing, the library defines a collection of IO types in addition to the istream and ostream types that we have already used. These types are defined in three separate headers: Header use iostream read from and write to a stream fstream read and write named files sstream read and write in-memory strings As we saw in previous, we cannot copy or assign objects of the IO types, so we cannot have a parameter or return type that is one of the stream types. Functions that do IO typically pass and return the stream through references. Inherent in doing IO is the fact that errors can occur. Some errors are recoverable; others occur deep within the system and are beyond the scope of a program to correct. The IO classes define functions and flags let us access and manipulate the condition state of a stream.(*stream represent one of streams in previous table) name meaning *stream::iostate Type for stream state flags *stream::eofbit End-Of-File reached while performing an extracting operation on an input stream. *stream::failbit The last input operation failed because of an error related to the internal logic of the operation itself. *stream::badbit Error due to the failure of an input/output operation on the stream buffer. *stream::goodbit No error. Represents the absence of all the above (the value zero). The more details you can read in here. As an example of an IO error, consider the following code:int ival; cin >> ival;, If we enter Boo on the standard input, the read will fail. The input operator expected to read an int but got the character B instead. As a result, cin will be put in an error state. Similarly, cin will be in an error state if we enter an end-of-file. Once an error has occurred, subsequent IO operations on that stream will fail. We can read from or write to a stream only when it is in a non-error state. File Input and OutputThe fstream header defines three types to support file IO: ifstream to read from a given file, ofstream to write to a given file, and fstream, which reads and writes a given file. These types provide the same operations as those we have previously used on the objects cin and cout. In particular, we can use the IO operators (<< and >>) to read and write files, we can use getline to read an ifstream. In addition to the behavior that they inherit from the iostream types, the types defined in fstream add members to manage the file associated with the stream. These operations, listed in next table, can be called on objects of fstream, ifstream, or ofstream but not on the other IO types. use meaning fstream fstrm; created a unbound file stream. fstream fstrm(s); created a file stream and open file called s fstream fstrm(s,mode); like previous, but with special mode. fstrm.open(s) Opens the file identified by argument filename fstrm.open(s,mode) like previous, but with special mode. fstrm.close() Closes the file currently associated with the object, disassociating it from the stream. fstrm.is_open() Returns whether the stream is currently associated to a file. When we want to read or write a file, we define a file stream object and associate that object with the file. Each file stream class defines a member function named open that does whatever system-specific operations are required to locate the given file and open it for reading or writing as appropriate. When we create a file stream, we can (optionally) provide a file name. When we supply a file name, open is called automatically, this is a simple example that create question and answer question program, the question and answer will be save in file. #include <iostream> #include <fstream> #include <string> int main() { //file path std::string ans_file = "Answer.txt"; std::string que_file = "Question.txt"; // created question std::ifstream in; std::ofstream out; out.open(que_file); out << 300 << " " << 400; out.close(); //answer the question in.open(que_file); int num1, num2, sum; in >> num1 >> num2; sum = num1 + num2; out.open(ans_file); out << sum; //read answer in.close(); in.open(ans_file); in >> sum; std::cout << sum << std::endl; return 0; } When we define an empty file stream object, we can subsequently associate that object with a file by calling open, If a call to open fails, failbit is set. Because a call to open might fail, it is usually a good idea to verify that the open succeeded: if(out){ /**/ } Once a file stream has been opened, it remains associated with the specified file. Indeed, calling open on a file stream that is already open will fail and set failbit. Subsequent attempts to use that file stream will fail. To associate a file stream with a different file, we must first close the existing file. Once the file is closed, we can open a new one. Each stream has an associated file mode that represents how the file may be used. Next table lists the file modes and their meanings. member constant stands for access in input File open for reading: the internal stream buffer supports input operations. out output File open for writing: the internal stream buffer supports output operations. binary binary Operations are performed in binary mode rather than text. ate at end The output position starts at the end of the file. app append All output operations happen at the end of the file, appending to its existing contents. trunc trunc ate Any contents that existed in the file before it is open are discarded. We can supply a file mode whenever we open a file—either when we call open or when we indirectly open the file when we initialize a stream from a file name. The modes that we can specify have the following restrictions: out may be set only for an ofstream or fstream object. in may be set only for an ifstream or fstream object. trunc may be set only when out is also specified app mode may be specified so long as trunc is not. If app is specified, the file is always opened in output mode, even if out was not explicitly specified. The ate and binary modes may be specified on any file stream object type and in combination with any other file modes. By default, when we open an ofstream, the contents of the file are discarded. The only way to prevent an ostream from emptying the given file is to specify app. string StreamsThe sstream header defines three types to support in-memory IO; these types read from or write to a string as if the string were an IO stream. The istringstream type reads a string, ostringstream writes a string, and stringstream reads and writes the string. Like the fstream types, the types defined in sstream inherit from the types we have used from the iostream header. In addition to the operations they inherit, the types defined in sstream add members to manage the string associated with the stream. These operations are listed in next table. They may be called on stringstream objects but not on the other IO types. use meaning sstream strm; unbound string stream sstream strm(s); string stream bound to string s strm.str() return a string that strm holds strm.str(s) copy string s to strm An istringstream is often used when we have some work to do on an entire line, and other work to do with individual words within a line. As one example, assume we have a file that lists people and their associated phone numbers. Some people have only one number, but others have several—a home phone, work phone, cell number, and so on. Our input file might look like the following:morgan 2015552368 8625550123, drew 9735550130, lee 6095550132 2015550175 8005550000. #include <iostream> #include <fstream> #include <sstream> #include <string> #include <vector> class Info { public: //constructor Info(std::string n) : name(n) {} //add more phone number void add_phone(std::string ph) { phone.push_back(ph); } //print information void print(std::ostream& os) { os << "Name: " << "\\n"; int count = 1; for (auto i = phone.begin(); i < phone.end(); i++) { os << "phone" << count << ": " << *i << "\\n"; } os << "\\n"; } private: std::string name; std::vector<std::string> phone; }; int main() { std::istringstream in; std::string info; std::vector<Info> contacts; while (getline(std::cin,info)) { in.str(info); std::string name; std::string phone_number; // read name and create contact in >> name; Info peo(name); //read phone number while (in >> phone_number) { peo.add_phone(phone_number); } contacts.push_back(peo); for (auto i = contacts.begin(); i < contacts.end(); i++) { i->print(std::cout); } } return 0; }","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch3) C++ Basic Features - Operators And Statements","slug":"Cpp_c03","date":"2021-12-03T13:52:45.000Z","updated":"2023-04-21T16:34:51.207Z","comments":true,"path":"2021/12/03/Cpp_c03/","link":"","permalink":"https://noahbishop.github.io/2021/12/03/Cpp_c03/","excerpt":"","text":"C++ Basic Features - Operators And StatementsExpressionsC++ provides a rich set of operators and defines what these operators do when applied to operands of built-in type. It also allows us to define the meaning of most of the operators when applied to operands of class types. This chapter focuses on the operators as defined in the language and applied to operands of built-in type. An expression is composed of one or more operands and yields a result when it is evaluated. The simplest form of an expression is a single literal or variable. The result of such an expression is the value of the variable or literal. More complicated expressions are formed from an operator and one or more operands. FundamentalsThe basic concept of expressions are operator and operands, the operator can be classified by operands, Unary operators, such as address-of (&) and dereference (*), act on one operand. Binary operators, such as equality (==) and multiplication (*), act on two operands. There is also one ternary operator that takes three operands, and one operator, function call, that takes an unlimited number of operands. Understanding expressions with multiple operators requires understanding the precedence and associativity of the operators and may depend on the order of evaluation of the operands. For example, 5 + 7 * 9, the * has high precedence. The language defines what the operators mean when applied to built-in and compound types. We can also define what most operators mean when applied to class types. Because such definitions give an alternative meaning to an existing operator symbol, we refer to them as overloaded operators. The IO library >> and << operators and the operators we used with strings, vectors, and iterators are all overloaded operators. When you write compound expressions, two rules of thumb can be helpful: When in doubt, parenthesize expressions to force the grouping that the logic of your program requires. If you change the value of an operand, don’t use that operand elsewhere in the same expression. Arithmetic OperatorsThe next table show arithmetic operators in c++ order by precedence. Operator function expression + unary plus + operand1 - unary minus - operand1 * multiplication operand1 * operand2 / division operand1 / operand2 % reminder operand1 % operand2 + addition operand1 + operand2 - subtraction operand1 - operand2 Note same operator has different meaning with different type of operand, when division a integer, like 5 / 2, the result is 2 rather than 2.5. Logical and Relational OperatorsThe relational operators take operands of arithmetic or pointer type; the logical operators take operands of any type that can be converted to bool. These operators all return values of type bool. Arithmetic and pointer operand(s) with a value of zero are false; all other values are true. The operands to these operators are rvalues and the result is an rvalue. (notice: because my blog convert sheet have error, I replace | symbol with ⏐) Operator function expression ! logical not ! operand > greater than operand1 > operand2 >= greater than or equal operand1 >= operand2 < less than operand1 < operand2 <= less than or equal operand1 <= operand2 == equal operand1 == operand2 != unequal operand1 != operand2 && logical and operand1 && operand2 ⏐⏐ logical or operand1 ⏐⏐ operand2 The Member Access OperatorsThe dot . and arrow -> operators provide for member access. The dot operator fetches a member from an object of class type; arrow is defined so that ptr->mem is a synonym for (*ptr).mem: The Conditional OperatorThe conditional operator (the ?: operator) lets us embed simple if-else logic inside an expression. The conditional operator has the following form: cond ? expr1 : expr2;. where cond is an expression that is used as a condition and expr1 and expr2 are expressions of the same type (or types that can be converted to a common type). This operator executes by evaluating cond. If the condition is true, then expr1 is evaluated; otherwise, expr2 is evaluated. As one example, we can use a conditional operator to determine whether a grade is pass or fail: The Bitwise OperatorsThe bitwise operators take operands of integral type that they use as a collection of bits. These operators let us test and set individual bits. We can also use these operators on a library type named bitset that represents a flexibly sized collection of bits (notice: because my blog convert sheet have error, I replace | symbol with ⏐) Operator function expression ~ bitwise not ~ operand >> right shift operand1 >> operand2 << left shift operand1 << operand2 & bitwise and operand1 & operand2 ^ bitwise xor operand1 ^ operand2 ⏐ bitwise or operand1 ⏐ operand2 The sizeof OperatorThe sizeof operator returns the size, in bytes, of an expression or a type name. The operator is right associative. The result of sizeof is a constant expression of type size_t. The operator takes one of two forms: sizeof (type) and sizeof expr. sizeof(int); int a = 10; sizeof a; Type Explicit ConversionsA named cast has the following form:cast-name<type>(expression); where type is the target type of the conversion, and expression is the value to be cast. If type is a reference, then the result is an lvalue. The cast-name may be one of static_cast, dynamic_cast, const_cast, and reinterpret_cast. A static_cast is often useful when a larger arithmetic type is assigned to a smaller type. The cast informs both the reader of the program and the compiler that we are aware of and are not concerned about the potential loss of precision. A static_cast is also useful to perform a conversion that the compiler will not generate automatically. For example, we can use a static_cast to retrieve a pointer value that was stored in a void* pointer: float a = 1; void* p = &a; // *k equal to 1065353216 (IEEE 754) // *l equal to 1 int *k = static_cast<int*>(p); float *l = static_cast<float*>(p); A const_cast changes only a low-level const in its operand: const int *a; auto p = const_cast<int*>(a); *p = 10; // ok *a = 10; // error Conventionally we say that a cast that converts a const object to a nonconst type “casts away the const.” Once we have cast away the const of an object, the compiler will no longer prevent us from writing to that object. If the object was originally not a const, using a cast to obtain write access is legal. However, using a const_cast in order to write to a const object is undefined. StatementsLike most languages, C++ provides statements for conditional execution, loops that repeatedly execute the same body of code, and jump statements that interrupt the flow of control. This chapter looks in detail at the statements supported by C++. The almost statements are same as other language like java, and be used in previous code, so the next only show some new features. try Blocks and Exception HandlingExceptions are run-time anomalies—such as losing a database connection or encountering unexpected input—that exist outside the normal functioning of a program. Dealing with anomalous behavior can be one of the most difficult parts of designing any system. In C++, exception handling involves: throw expressions, which the detecting part uses to indicate that it encountered something it can’t handle. We say that a throw raises an exception. try blocks, which the handling part uses to deal with an exception. A try block starts with the keyword try and ends with one or more catch clauses. A set of exception classes that are used to pass information about what happened between a throw and an associated catch. Supposed wo want write a program that need read two numbers and then will do division, as we know, zero can’t be divisor. We need throw a arithmetic error if divisor is zero. #include <iostream> int main() { int num1, num2; while (std::cin >> num1 >> num2){ if (num2 == 0){ throw std::runtime_error("divisor can't be zero!"); }else{ std::cout << num1 << "/" << num2 << "=" << num1 / num2 << std::endl; } } return 0; } Only throw error is no a good idea, we also need catch the error so that program will continued running, and report the error to user: #include <iostream> int main() { int num1, num2; while (std::cin >> num1 >> num2) { try{ if (num2 == 0) { throw std::runtime_error("divisor can't be zero!"); } else { std::cout << num1 << "/" << num2 << "=" << num1 / num2 << std::endl; } } catch (std::runtime_error err){ std::cout << err.what() << std::endl; std::cout << "please input a correct divisor" << std::endl; } } return 0; } If no appropriate catch is found, execution is transferred to a library function named terminate. The behavior of that function is system dependent but is guaranteed to stop further execution of the program. Exceptions that occur in programs that do not define any try blocks are handled in the same manner: After all, if there are no try blocks, there can be no handlers. If a program has no try blocks and an exception occurs, then terminate is called and the program is exited. The C++ library defines several classes that it uses to report problems encountered in the functions in the standard library. These exception classes are also intended to be used in the programs we write. These classes are defined in four headers: The exception header defines the most general kind of exception class named exception. It communicates only that an exception occurred but provides no additional information. The stdexcept header defines several general-purpose exception classes. The new header defines the bad_alloc exception type. The type_info header defines the bad_cast exception type. Functions And ClassesFunctionA function is a block of code with a name. We execute the code by calling the function. A function may take zero or more arguments and (usually) yields a result. Functions can be overloaded, meaning that the same name may refer to several different functions. Function BasicsA function definition typically consists of a return type, a name, a list of zero or more parameters, and a body. The parameters are specified in a comma-separated list enclosed in parentheses. The actions that the function performs are specified in a statement block, referred to as the function body. example: int fact(int n) { //The factorial of n int sum = 1; while (n>0) { sum *= n--; } return sum; } To call fact, we must supply an int value. The result of the call is also an int: int main() { int f = 0; std::cin >> f; std::cout << fact(f) << std::endl; return 0; } A function call does two things: It initializes the function’s parameters from the corresponding arguments. It transfers control to that function. Execution of the calling function is suspended and execution of the called function begins. Most types can be used as the return type of a function. In particular, the return type can be void, which means that the function does not return a value. However, the return type may not be an array type or a function type. However, a function may return a pointer to an array or a function. In C++, names have scope, and objects have lifetimes. It is important to understand both of these concepts. The scope of a name is the part of the program’s text in which that name is visible. The lifetime of an object is the time during the program’s execution that the object exists. Parameters and variables defined inside a function body are referred to as local variables. They are “local” to that function and hide declarations of the same name made in an outer scope. The objects that correspond to ordinary local variables are created when the function’s control path passes through the variable’s definition. They are destroyed when control passes through the end of the block in which the variable is defined. Objects that exist only while a block is executing are known as automatic objects. After execution exits a block, the values of the automatic objects created in that block are undefined. It can be useful to have a local variable whose lifetime continues across calls to the function. We obtain such objects by defining a local variable as static. Each local static object is initialized before the first time execution passes through the object’s definition. Local statics are not destroyed when a function ends; they are destroyed when the program terminates. This program will print the numbers from 1 through 10 inclusive: size_t count_calls(){ static size_t ctr = 0; // value will persist across calls return ++ctr; } int main(){ for (size_t i = 0; i != 10; ++i) cout << count_calls() << endl; return 0; } Like any other name, the name of a function must be declared before we can use it. As with variables, a function may be defined only once but may be declared multiple times. A function declaration is just like a function definition except that a declaration has no function body. In a declaration, a semicolon replaces the function body. Argument PassingAs we’ve seen, each time we call a function, its parameters are created and initialized by the arguments passed in the call. As with any other variable, the type of a parameter determines the interaction between the parameter and its argument. If the parameter is a reference, then the parameter is bound to its argument. Otherwise, the argument’s value is copied. When a parameter is a reference, we say that its corresponding argument is “passed by reference” or that the function is “called by reference.”When the argument value is copied, the parameter and argument are independent objects. We say such arguments are “passed by value” or alternatively that the function is “called by value.” #include <iostream> using std::cin; using std::cout; using std::endl; void toUper1(char c) { c = toupper(c); } void toUper2(char &c) { c = toupper(c); } int main(){ char c = 'a'; //no effect, because c in to toUper1 is a copy of c in in to main toUper1(c); cout << c << endl;// print a //effect, because c in to toUper1 is bound to c in in to main toUper2(c); cout << c << endl;//print A return 0; } Pointers behave like any other nonreference type. When we copy a pointer, the value of the pointer is copied. After the copy, the two pointers are distinct. However, a pointer also gives us indirect access to the object to which that pointer points. We can change the value of that object by assigning through the pointer: void toUper3(char *c) { *c = toupper(*c); } //note in mian wo call this function need pass the address of c //toUper3(&c) It can be inefficient to copy objects of large class types or large containers. Moreover, some class types (including the IO types) cannot be copied. Functions must use reference parameters to operate on objects of a type that cannot be copied. As an example, we’ll write a function to compare the length of two strings. Because strings can be long, we’d like to avoid copying them, so we’ll make our parameters references. Arrays have two special properties that affect how we define and use functions that operate on arrays: We cannot copy an array, and when we use an array it is (usually) converted to a pointer. Because we cannot copy an array, we cannot pass an array by value. Because arrays are converted to pointers, when we pass an array to a function, we are actually passing a pointer to the array’s first element. Even though we cannot pass an array by value, we can write a parameter that looks array: Regardless of appearances, these declarations are equivalent: Each declares a function with a single parameter of type const int*. When the compiler checks a call to print, it checks only that the argument has type const int*: void print(const int*); void print(const int[]); // shows the intent that the function takes an array void print(const int[10]); // dimension for documentation purposes (at best) Because arrays are passed as pointers, functions ordinarily don’t know the size of the array they are given. They must rely on additional information provided by the caller. There are three common techniques used to manage pointer parameters. A technique used to manage array arguments is to pass pointers to the first and one past the last element in the array. This approach is inspired by techniques used in the standard library. Using this approach, we’ll print the elements in an array as follows: #include <iostream> using std::cin; using std::cout; using std::endl; void print(const int *beg, const int *end) { while (beg != end) { cout << *beg++ << endl; } } int main(){ int arr[4] = { 2,4,2,4 }; print(std::begin(arr), std::end(arr)); return 0; } Another approach for array arguments, which is common in C programs and older C++ programs, is to define a second parameter that indicates the size of the array. Using this approach, we’ll rewrite print as follows: void print(const int ia[], size_t size) { for (size_t i = 0; i != size; ++i) { cout << ia[i] << endl; } } In c++ multidimensional array is an array of arrays. As with any array, a multidimensional array is passed as a pointer to its first element. Because we are dealing with an array of arrays, that element is an array, so the pointer is a pointer to an array. The size of the second (and any subsequent) dimension is part of the element type and must be specified: void print(int (*arr)[2], size_t size) { for (size_t i = 0; i < size; i++) { auto beg = std::begin(*arr), end = std::end(*arr); while (beg != end) { cout << *beg++ << endl; } arr += 1; } } //We can also define our function using array syntax. void print(int arr[][2], size_t size) { /* . . . */ } Up to now, we have defined main with an empty parameter list: int main() { ... }, However, we sometimes need to pass arguments to main. The most common use of arguments to main is to let the user specify a set of options to guide the operation of the program. For example, assuming our main program is in an executable file named prog, we might pass options to the program as follows:prog -d -o ofile data0. Such command-line options are passed to main in two (optional) parameters: int main(int argc, char *argv[]) { ... }, The second parameter, argv, is an array of pointers to C-style character strings. Thefirst parameter, argc, passes the number of strings in that array. Sometimes we do not know in advance how many arguments we need to pass to a function. For example, we might want to write a routine to print error messages generated from our program. We’d like to use a single function to print these error messages in order to handle them in a uniform way. However, different calls to our error-printing function might pass different arguments, corresponding to different kinds of error messages. The new standard provides two primary ways to write a function that takes a varying number of arguments: If all the arguments have the same type, we can pass a library type named initializer_list. An initializer_list is a library type that represents an array of values of the specified type. This type is defined in the initializer_list header. You can see all details in here. void error_msg(std::initializer_list<std::string> il){ for (auto beg = il.begin(); beg != il.end(); ++beg) cout << *beg << " "; cout << endl; } When we pass a sequence of values to an initializer_list parameter, we must enclose the sequence in curly braces: error_msg({ "4tw", "98ty8w" }); Return Types and the return StatementA return statement terminates the function that is currently executing and returns control to the point from which the function was called. There are two forms of return statements: return; and return expression;. A return with no value may be used only in a function that has a return type of void. Functions that return void are not required to contain a return. In a void function, an implicit return takes place after the function’s last statement. The second form of the return statement provides the function’s result. Every return in a function with a return type other than void must return a value. Note never return a reference or pointer to a local object, When a function completes, its storage is freed. After a function terminates, references to local objects refer to memory that is no longer valid: //don't do this std::string& em() { std::string str = "iecbvasj"; return str; } A function that calls itself, either directly or indirectly, is a recursive function. As an example, we can rewrite our factorial function to use recursion: int factorial(int val){ if (val > 1) return factorial(val-1) * val; return 1; } Because we cannot copy an array, a function cannot return an array. However, a function can return a pointer or a reference to an array. There are some ways to simplify such declarations, the most straightforward way is to use a type alias: typedef int arrT[10]; // arrT is a synonym for the type array of ten ints using arrT = int[10]; // equivalent declaration of arrT; arrT is a synonym for an array of ten ints. Because we cannot return an array, we define the return type as a pointer to this type. // a function that initiated a array arrT *init_arr(arrT *t){ for (auto i = std::begin(*t); i != std::end(*t); i++){ *i = 1; } return t; } To declare function without using a type alias, we must remember that the dimension of an array follows the name being defined: int (*init_arr1(int (*t)[10]))[10] { for (auto i = std::begin(*t); i != std::end(*t); i++) { *i = 1; } return t; } Under the new standard, another way to simplify the declaration of func is by using a trailing return type. Trailing returns can be defined for any function, but are most useful for functions with complicated return types, such as pointers (or references) to arrays. auto init_arr(arrT* t) -> int(*)[10]{ for (auto i = std::begin(*t); i != std::end(*t); i++) { *i = 1; } return t; } Overloaded FunctionsFunctions that have the same name but different parameter lists and that appear in the same scope are overloaded. For example, we want write a print function that print array with different parameter: void print(const char *cp); void print(const int *beg, const int *end); void print(const int ia[], size_t size); When we call these functions, the compiler can deduce which function we want based on the argument type we pass, Overloaded functions must differ in the number or the type(s) of their parameters. As we saw in previous top-level const has no effect on the objects that can be passed to the function. A parameter that has a top-level const is indistinguishable from one without a top-level const: int f1(const int a); int f1(int a); //same int f1(int *const a); int f1(int* a); //same In these declarations, the second declaration declares the same function as the first.But we can overload based on whether the parameter is a reference (or pointer) to the const or nonconst version of a given type; such consts are low-level: because a low level pointer or reference refer a const object, In these cases, the compiler can use the constness of the argument to distinguish which function to call: #include <iostream> using std::cin; using std::cout; using std::endl; int f1(const int* a) { return 1; } int f1(int* a) { return 2; } int f1(const int& a) { return 3; } int f1(int& a) { return 4; } int main() { const int a = 0; int b = 0; cout << f1(&a) << endl; //print 1 cout << f1(&b) << endl; //print 2 cout << f1(a) << endl; //print 3 cout << f1(b) << endl; //print 4 return 0; } Note overloading has no special properties with respect to scope: As usual, if we declare a name in an inner scope, that name hides uses of that name declared in an outer scope. Names do not overload across scopes: #include <iostream> using std::cin; using std::cout; using std::endl; int z() { return 10; } int p() { return 1; } int main() { bool z; int r = z(); //error: z is a bool object double p(int a); p(); //error: previous function is hided p(1); //ok return 0; } Features for Specialized UsesIn this section we’ll cover three function-related features that are useful in many, but not all, programs: default arguments, inline and constexpr functions. Some functions have parameters that are given a particular value in most, but not all. In such cases, we can declare that common value as a default argument for the function. Functions with default arguments can be called with or without thatargument. For example, we want say hello to different program languages, in default case we say hello to c++ once: #include <iostream> #include <string> using std::cin; using std::cout; using std::endl; void hello(std::string s = "C++", size_t n = 1) { for (size_t i = 0; i < n; i++) { cout << "Hello " << s << endl; } } int main() { hello(); // say hello C++ once hello("Java"); // say hello Java once hello("C#", 2);// say hello C# twice hello(3); // error: can omit only trailing arguments return 0; } Sometimes we defining a function with such a small operation, the benefit of function are: It is easier to read and understand a call to function than it would be to read and understand the equivalent conditional expression. Using a function ensures uniform behavior. Each test is guaranteed to be done the same way. If we need to change the computation, it is easier to change the function than to find and change every occurrence of the equivalent expression. The function can be reused rather than rewritten for other applications. However, Calling a function is apt to be slower than evaluating the equivalent expression. On most machines, a function call does a lot of work: Registers are saved before the call and restored after the return; arguments may be copied; and the program branches to a new location. Fortunately we have a good way to fix it disadvantage. The Inline Functions A function specified as inline (usually) is expanded “in line” at each call. If function were defined as inline, then this call (probably) would be expanded during compilation into expression. inline const string &shorterString(const string &s1, const string &s2){ return s1.size() <= s2.size() ? s1 : s2; } cout << shorterString(s1, s2) << endl; //may be compilation into expression. //cout << (s1.size() < s2.size() ? s1 : s2) << endl; In general, the inline mechanism is meant to optimize small, straight-line functions that are called frequently. Function MatchingIn many (if not most) cases, it is easy to figure out which overloaded function matches a given call. However, it is not so simple when the overloaded functions have the same number of parameters and when one or more of the parameters have types that are related by conversions. The step of function matching as follow: The first step of function matching identifies the set of overloaded functions considered for the call. The functions in this set are the candidate functions. A candidate function is a function with the same name as the called function and for which a declaration is visible at the point of the call. The second step selects from the set of candidate functions those functions that can be called with the arguments in the given call. The selected functions are the viable functions. To be viable, a function must have the same number of parameters as there are arguments in the call, and the type of each argument must match—or be convertible to—the type of its corresponding parameter. The third step of function matching determines which viable function provides the best match for the call. This process looks at each argument in the call and selects the viable function (or functions) for which the corresponding parameter best matches the argument. The idea is that the closer the types of the argument and parameter are to each other, the better the match. In order to determine the best match, the compiler ranks the conversions that could be used to convert each argument to the type of its corresponding parameter. Conversions are ranked as follows: An exact match. Match through a const conversion Match through a promotion Match through an arithmetic or pointer conversion Match through a class-type conversion. Pointers to FunctionsA function pointer is just that—a pointer that denotes a function rather than an object. Like any other pointer, a function pointer points to a particular type. A function’s type is determined by its return type and the types of its parameters. The function’s name is not part of its type. For example: // compare two int bool cmp(int a, int b) { return a > b; } // pf points to a function returning bool that takes two int bool (*pf)(int a, int b); When we use the name of a function as a value, the function is automatically converted to a pointer. For example, we can assign the address of cmp to pf as follows: pf = cmp; pf = &cmp; Moreover, we can use a pointer to a function to call the function to which the pointer points. We can do so directly—there is no need to dereference the pointer: bool a = (*cmp)(1, 2); bool b = cmp(1, 2); bool c = (*pf)(1, 2); bool d = pf(1, 2); As usual, when we use an overloaded function, the context must make it clear which version is being used. When we declare a pointer to an overloaded function the compiler uses the type of the pointer to determine which overloaded function to use. The type of the pointer must match one of the overloaded functions exactly: void ff(int*); void ff(unsigned int); void (*pf1)(unsigned int) = ff; // ok: pf1 points to ff(unsigned int) void (*pf2)(int) = ff; // error: no ff with a matching parameter list double (*pf3)(int*) = ff; // error: return type of ff and pf3 don't match Just as with arrays, we cannot define parameters of function type but can have a parameter that is a pointer to function. As with arrays, we can write a parameter that looks like a function type, but it will be treated as a pointer: void useBigger(const string& s1, const string& s2, bool pf(const string&, const string&)); // equivalent declaration: explicitly define the parameter as a pointer to function void useBigger(const string& s1, const string& s2, bool (*pf)(const string&, const string&)); When we pass a function as an argument, we can do so directly. It will be automatically converted to a pointer. As with arrays, we can’t return a function type but can return a pointer to a function type. Similarly, we must write the return type as a pointer type; the compiler will not automatically treat a function return type as the corresponding pointer type. Also as with array returns, by far the easiest way to declare a function that returns a pointer to function is by using a type alias: using F = int(int, int); // F is a function type, not a pointer using pF = int(*)(int, int);// pF is a pointer pF f1(int); // ok: pF is a pointer to function; f1 returns a pointer to function F f2(int); // error: F is a function type; f1 can't return a function F *f3(int); // ok: explicitly specify that the return type is a pointer to function For completeness, it’s worth noting that we can simplify declarations of functions that return pointers to function by using a trailing return: auto f1(int) -> int (*)(int*, int); ClassesIn C++ we use classes to define our own data types. By defining types that mirror concepts in the problems we are trying to solve, we can make our programs easier to write, debug, and modify. Defining Abstract Data TypesOur Sales_data class(in previous note) is not an abstract data type. It lets users of the class access its data members and forces users to write their own operations. To make Sales_data an abstract type, we need to define operations for users of Sales_data to use. Once Sales_data defines its own operations, we can encapsulate (that is, hide) its data members. Ultimately, we want Sales_data to support the same set of operations as the Sales_item class. The Sales_item class had one member function named isbn, and supported the +, =, +=, <<, and >> operators. We’ll learn how to define our own operators in the next post. For now, we’ll define ordinary (named) functions for these operations. Thus, the interface to Sales_data consists of the following operations: An isbn member function to return the object’s ISBN A combine member function to add one Sales_data object into another A function named add to add two Sales_data objects A read function to read data from an istream into a Sales_data object A print function to print the value of a Sales_data object on an ostream Before we think about how to implement our class, let’s look at how we can use our interface functions. As one example, we can use these functions to write a version of the bookstore program that works with Sales_data objects rather than Sales_items: #include <iostream> #include <string> #include "Sales_data.h" using std::cin; using std::cout; using std::endl; int main() { Sales_data total; // variable to hold the running sum if (read(cin, total)) { // read the first transaction Sales_data trans; // variable to hold data for the next transaction while (read(cin, trans)) { // read the remaining transactions if (total.isbn() == trans.isbn()) // check the isbns total.combine(trans); // update the running total else { print(cout, total) << endl; // print the results total = trans; // process the next book } } print(cout, total) << endl; // print the last transaction } else { // there was no input std::cerr << "No data?!" << endl; // notify the user } return 0; } Our revised class will have the same data members as the version we defined in previous: bookNo, a string representing the ISBN; units_sold, an unsigned that says how many copies of the book were sold; and revenue, a double representing the total revenue for those sales. As we’ve seen, our class will also have two member functions, combine and isbn. In addition, we’ll give Sales_data another member function to return the average price at which the books were sold. This function, which we’ll name avg_price, isn’t intended for general use. It will be part of the implementation, not part of the interface. We define and declare member functions similarly to ordinary functions. Member functions must be declared inside the class. Member functions may be defined inside the class itself or outside the class body. Nonmember functions that are part of the interface, such as add, read, and print, are declared and defined outside the class. #ifndef SALES_DATA_H #define SALES_DATA_H #include <string> struct Sales_data { // new members: operations on Sales_data objects std::string isbn() const { return bookNo; } Sales_data& combine(const Sales_data&); double avg_price() const; std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; // nonmember Sales_data interface functions Sales_data add(const Sales_data&, const Sales_data&); std::ostream& print(std::ostream&, const Sales_data&); std::istream& read(std::istream&, Sales_data&); #endif We’ll start by explaining the isbn function, which returns a string and has an empty parameter list. As with any function, the body of a member function is a block. In this case, the block contains a single return statement that returns the bookNo data member of a Sales_data object. The interesting thing about this function is how it gets the object from which to fetch the bookNo member. Let’s look again at a call to the isbn member function: total.isbn(),Here we use the dot operator to fetch the isbn member of the object named total, which we then call. When isbn refers to members ofSales_data, it is referring implicitly to the members of the object on which the function was called. In this call, when isbn returns bookNo, it is implicitly returning total.bookNo. Member functions access the object on which they were called through an extra, implicit parameter named this. When we call a member function, this is initialized with the address of the object on which the function was invoked. For example, when we call total.isbn(), the compiler passes the address of total to the implicit this parameter in isbn. It is as if the compiler rewrites this call as Sales_data::isbn(&total), which calls the isbn member of Sales_data passing the address of total. The this parameter is defined for us implicitly. Indeed, it is illegal for us to define a parameter or variable named this. Inside the body of a member function, we can use this. It would be legal, although unnecessary, to define isbn as: std::string isbn() const { return this->bookNo; } The other important part about the isbn function is the keyword const that follows the parameter list. The purpose of that const is to modify the type of the implicit this pointer. By default, the type of this is a const pointer to the nonconst version of the class type. For example, by default, the type of this in a Sales_data member function is Sales_data *const. Although this is implicit, it follows the normal initialization rules, which means that (by default) we cannot bind this to a const object. This fact, in turn, means that we cannot call an ordinary member function on a const object. If isbn were an ordinary function and if this were an ordinary pointer parameter, we would declare this as const Sales_data *const. After all, the body of isbn doesn’t change the object to which this points, so our function would be more flexible if this were a pointer to const. Member functions that use const in this way are const member functions. The fact that this is a pointer to const means that const member functions cannot change the object on which they are called. Thus, isbn may read but not write to the data members of the objects on which it is called. The definitions of the member functions of a class are nested inside the scope of the class itself. Hence, isbn’s use of the name bookNo is resolved as the data member defined inside Sales_data. It is worth noting that isbn can use bookNo even though bookNo is defined after isbn. Because the compiler processes classes in two steps— the member declarations are compiled first, after which the member function bodies, if any, are processed. Thus, member function bodies may use other members of their class regardless of where in the class those members appear. As with any other function, when we define a member function outside the class body, the member’s definition must match its declaration. That is, the return type, parameter list, and name must match the declaration in the class body. // define avg price function double Sales_data::avg_price() const { if (units_sold) return revenue / units_sold; else return 0; } The function name, Sales_data::avg_price, uses the scope operator to say that we are defining the function named avg_price that is declared in the scope of the Sales_data class. Once the compiler sees the function name, the rest of the code is interpreted as being inside the scope of the class. Thus, when avg_price refers to revenue and units_sold, it is implicitly referring to the members of Sales_data. The combine function is intended to act like the compound assignment operator, +=. The object on which this function is called represents the left-hand operand of the assignment. The right-hand operand is passed as an explicit argument: // define combine function Sales_data& Sales_data::combine(const Sales_data& rhs) { units_sold += rhs.units_sold; // add the members of rhs into revenue += rhs.revenue; // the members of ''this'' object return *this; // return the object on which the function was called } When our transaction-processing program calls total.combine(trans);, the address of total is bound to the implicit this parameter and rhs is bound to trans. At the end, Here the return statement dereferences this to obtain the object on which the function is executing. That is, for the call above, we return a reference to total. Class authors often define auxiliary functions, such as our add, read, and print functions. Although such functions define operations that are conceptually part of the interface of the class, they are not part of the class itself. We define nonmember functions as we would any other function. As with any other function, we normally separate the declaration of the function from its definition. Functions that are conceptually part of a class, but not defined inside the class, are typically declared (but not defined) in the same header as the class itself. That way users need to include only one file to use any part of the interface. The read and print functions do the same job as the code in previous code: std::ostream& print(std::ostream& os, const Sales_data& item) { os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price(); return os; } std::istream& read(std::istream& is, Sales_data& item) { double price = 0; is >> item.bookNo >> item.units_sold >> price; item.revenue = item.units_sold * price; return is; } The add function takes two Sales_data objects and returns a new Sales_data representing their sum: Sales_data add(const Sales_data& lhs, const Sales_data& rhs) { Sales_data sum = lhs; sum.combine(rhs); return sum; } Each class defines how objects of its type can be initialized. Classes control object initialization by defining one or more special member functions known as constructors. The job of a constructor is to initialize the data members of a class object. A constructor is run whenever an object of a class type is created. Constructors have the same name as the class. Unlike other functions, constructors have no return type. Like other functions, constructors have a (possibly empty) parameter list and a (possibly empty) function body. A class can have multiple constructors. Like any other overloaded function, the constructors must differ from each other in the number or types of their parameters. Our Sales_data class does not define any constructors, yet the programs we’ve written that use Sales_data objects compile and run correctly. So, How are they initialized? The answer is if our class does not explicitly define any constructors, the compiler will implicitly define the default constructor for us. For our Sales_data class we’ll define four constructors with the following parameters: An istream& from which to read a transaction. A const string& representing an ISBN, an unsigned representing the count of how many books were sold, and a double representing the price at which the books sold. A const string& representing an ISBN. This constructor will use default values for the other members. An empty parameter list (i.e., the default constructor) which as we’ve just seen we must define because we have defined other constructors. // constructors added // means default constructor Sales_data() = default; Sales_data(std::string& s) : bookNo(s) {} Sales_data(std::string& s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p* n) {} Sales_data(std::istream&); It is worth noting that both constructors have empty function bodies. The only work these constructors need to do is give the data members their values. If there is no further work, then the function body is empty. Unlike our other constructors, the constructor that takes an istream does have work to do. Inside its function body, this constructor calls read to give the data members new values: Sales_data::Sales_data(std::istream& is) { read(is, *this); } There are something need to notice: As with any other member function, when we define a constructor outside of the class body, we must specify the class of which the constructor is a member. In this constructor there is no constructor initializer list, although technically speaking, it would be more correct to say that the constructor initializer list is empty. Even though the constructor initializer list is empty, the members of this object are still initialized before the constructor body is executed. Access Control and EncapsulationAt this point, we have defined an interface for our class; but nothing forces users to use that interface. Our class is not yet encapsulated—users can reach inside a Sales_data object and meddle with its implementation. In C++ we use access specifiers to enforce encapsulation: Members defined after a public specifier are accessible to all parts of the program. The public members define the interface to the class. Members defined after a private specifier are accessible to the member functions of the class but are not accessible to code that uses the class. struct Sales_data { public: // constructors added Sales_data() = default; Sales_data(std::string& s) : bookNo(s) {} Sales_data(std::string& s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p* n) {} Sales_data(std::istream&); // new members: operations on Sales_data objects std::string isbn() const; Sales_data& combine(const Sales_data&); private: double avg_price() const; std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; A class may contain zero or more access specifiers, and there are no restrictions on how often an access specifier may appear. Each access specifier specifies the access level of the succeeding members. The specified access level remains in effect until the next access specifier or the end of the class body. We also made another, more subtle, change: We used the class keyword rather than struct to open the class definition. This change is strictly stylistic; we can define a class type using either keyword. The only difference between struct and class is the default access level. A class may define members before the first access specifier. Access to such members depends on how the class is defined. If we use the struct keyword, the members defined before the first access specifier are public; if we use class, then the members are private. Now that the data members of Sales_data are private, our read, print, and add functions will no longer compile. The problem is that although these functions are part of the Sales_data interface, they are not members of the class. A class can allow another class or function to access its nonpublic members by making that class or function a friend. A class makes a function its friend by including a declaration for that function preceded by the keyword friend: // add into class friend Sales_data add(const Sales_data&, const Sales_data&); friend std::ostream& print(std::ostream&, const Sales_data&); friend std::istream& read(std::istream&, Sales_data&); Encapsulation provides two important advantages: User code cannot inadvertently corrupt the state of an encapsulated object. The implementation of an encapsulated class can change over time without requiring changes in user-level code. Additional Class FeaturesThe Sales_data class is pretty simple, in the next we’ll cover some additional class-related features that Sales_data doesn’t need to use. These features include type members, in-class initializers for members of class type, mutable data members, inline member functions, returning *this from a member function, more about how we define and use class types, and class friendship. First we will talk about type members, suppose we want use a class called Screen represents a window on a display. Each Screen has a string member that holds the Screen’s contents, and three string::size_type members that represent the position of the cursor, and the height and width of the screen. In addition to defining data and function members, a class can define its own local names for types. Type names defined by a class are subject to the same access controls as any other member and may be either public or private: class Screen { public: using pos = std::string::size_type; private: std::string contents; pos height; pos height; pos cursor; }; We defined pos in the public part of Screen because we want users to use that name. we can use Screen::pos k = 10; after include header file. #ifndef Screen_H #define Screen_H #include <string> class Screen { public: using pos = std::string::size_type; // default constructor and another constructor Screen() = default; Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht* wd, c) {} // get the character at the cursor // implicitly inline char get() const { return contents[cursor]; } // explicitly inline inline char get(pos r, pos c) const { pos row = r * width; return contents[row + c]; } // move cursor position Screen& move(pos r, pos c) { pos row = r * width; cursor = row + c; return *this; } // test void test_mutable() const { ++access_ctr; } private: mutable pos access_ctr = 0; std::string contents; pos height = 0; pos width = 0; pos cursor = 0; }; #endif // !Screen_H Classes often have small functions that can benefit from being inlined. Member functions defined inside the class are automatically inline. As with nonmember functions, member functions may be overloaded so long as the functions differ by the number and/or types of parameters. In the screen class, the second get function is a overload function. Screen::pos h = 10; Screen::pos w = 10; Screen myscreen(h,w,'A'); cout << myscreen.get(0,0) << endl; cout << myscreen.get() << endl; It sometimes (but not very often) happens that a class has a data member that we want to be able to modify, even inside a const member function. We indicate such members by including the mutable keyword in their declaration. A mutable data member is never const, even when it is a member of a const object. Accordingly, a const member function may change a mutable member. As an example, we’ll give Screen a mutable member named access_ctr, and a const member function that change the value of mutable member. The function can be correct use. Next we’ll add functions to set the character at the cursor or at a given location: #ifndef Screen_H #define Screen_H #include <string> class Screen { public: // other members function as before // set char in screen Screen &set(char c) { contents[cursor] = c; return *this; } Screen &set(pos r, pos col, char c) { contents[r * width + col] = c; return *this; } // other members as before }; #endif // !Screen_H Like the move operation, our set members return a reference to the object on which they are called. Functions that return a reference are lvalues, which means that they return the object itself, not a copy of the object. If we concatenate a sequence of these actions into a single expression: myscreen.move(4, 0).set('B'); That is, this statement is equivalent to: myscreen.move(4, 0); myscreen.set('B'); Next, we’ll add an operation, which we’ll name display, to print the contents of the Screen. We’d like to be able to include this operation in a sequence of set and move operations. Therefore, like set and move, our display function will return a reference to the object on which it executes. Logically, displaying a Screen doesn’t change the object, so we should make display a const member. If display is a const member, then this is a pointer to const and *this is a const object. Hence, the return type of display must be const Sales_data&. However, if display returns a reference to const, we won’t be able to embed display into a series of actions: myScreen.display(std::cout).set('B'); We can overload a member function based on whether it is const for the same reasons that we can overload a function based on whether a pointer parameter points to const. The nonconst version will not be viable for const objects; we can only call const member functions on a const object. We can call either version on a nonconst object, but the nonconst version will be a better match. public: //print screen const Screen& print(std::ostream& os)const { do_display(os); return *this; } Screen& print(std::ostream& os) { do_display(os); return *this; } private: void do_display(std::ostream& os) const { for (size_t i = 0; i < height; i++) { for (size_t j = 0; j < width; j++) { os << get(i, j); } os << std::endl; } } Our Sales_data class defined three ordinary nonmember functions as friends. A class can also make another class its friend or it can declare specific member functions of another (previously defined) class as friends. In addition, a friend function can be defined inside the class body. Such functions are implicitly inline. For example, we want use a class called Windows_mgr to control a set of screen, such as clear screen. To do this job, our function called clear needs to access the private data members of Screen. To allow this access, Screen can designate Window_mgr as its friend: class Screen { // Window_mgr members can access the private parts of class Screen friend class Windows_mgr; // ... rest of the Screen class }; class Windows_mgr { public: using ScreenIndex = std::vector<Screen>::size_type; Windows_mgr() = default; // clear screen void clear(ScreenIndex i) { Screen& s = screens[i]; s.contents = std::string(s.width * s.height, ' '); } private: std::vector<Screen> screens{ Screen(10,10,' ') }; }; Rather than making the entire Window_mgr class a friend, Screen can instead specify that only the clear member is allowed access. When we declare a member function to be a friend, we must specify the class of which that function is a member: class Screen { // clear function in Windoes_mgr can access the private parts of class Screen friend void Windows_mgr::clear(ScreenIndex i); // ... rest of the Screen class }; Making a member function a friend requires careful structuring of our programs to accommodate interdependencies among the declarations and definitions. In this example, we must order our program as follows: First, define the Window_mgr class, which declares, but cannot define, clear. Screen must be declared before clear can use the members of Screen. Next, define class Screen, including a friend declaration for clear. Finally, define clear, which can now refer to the members in Screen. The full code: #ifndef Screen_H #define Screen_H #include <string> #include <vector> class Screen; class Windows_mgr { public: using ScreenIndex = std::vector<Screen>::size_type; Windows_mgr() = default; // clear screen void clear(ScreenIndex i); // add a new screen void add(); private: std::vector<Screen> screens; }; class Screen { public: friend void Windows_mgr::clear(ScreenIndex i); using pos = std::string::size_type; // default constructor and another constructor Screen() = default; Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht* wd, c) {} // get the character at the cursor // implicitly inline char get() const { return contents[cursor]; } // explicitly inline inline char get(pos r, pos c) const { pos row = r * width; return contents[row + c]; } // move cursor position Screen& move(pos r, pos c) { pos row = r * width; cursor = row + c; return *this; } // test mutable mumber void test_mutable() const { ++access_ctr; } //set char in screen Screen& set(char c) { contents[cursor] = c; return *this; } Screen& set(pos r, pos col, char c) { contents[r * width + col] = c; return *this; } //print screen const Screen& print(std::ostream& os)const { do_display(os); return *this; } Screen& print(std::ostream& os) { do_display(os); return *this; } private: mutable pos access_ctr = 0; std::string contents; pos height = 0; pos width = 0; pos cursor = 0; void do_display(std::ostream& os) const { for (size_t i = 0; i < height; i++) { for (size_t j = 0; j < width; j++) { os << get(i, j); } os << '\\n'; } } }; void Windows_mgr::clear(ScreenIndex i) { Screen& s = screens[i]; s.contents = std::string(s.width * s.height, ' '); } void Windows_mgr::add() { Screen new_s(10, 10, ' '); screens.push_back(new_s); } #endif // !Screen_H Class ScopeIn the programs we’ve written so far, name lookup (the process of finding which declarations match the use of a name) has been relatively straightforward: First, look for a declaration of the name in the block in which the name was used. Only names declared before the use are considered. If the name isn’t found, look in the enclosing scope(s). If no declaration is found, then the program is in error. The way names are resolved inside member functions defined inside the class may seem to behave differently than these lookup rules. However, in this case, appearances are deceiving. Class definitions are processed in two phases: First, the member declarations are compiled. Function bodies are compiled only after the entire class has been seen. Because member function bodies are not processed until the entire class is seen, they can use any name defined inside the class. If function definitions were processed at the same time as the member declarations, then we would have to order the member functions so that they referred only to names already seen. Constructors RevisitedConstructors are a crucial part of any C++ class. In this section we’ll cover some additional capabilities of constructors, and deepen our coverage of the material introduced earlier. When we define variables, we typically initialize them immediately rather than defining them and then assigning to them, exactly the same distinction between initialization and assignment applies to the datamembers of objects. If we do not explicitly initialize a member in the constructor initializer list, that member is default initialized before the constructor body starts executing. For example: // legal but sloppier way to write the Sales_data constructor: no constructor initializers Sales_data::Sales_data(const string& s, unsigned cnt, double price) { bookNo = s; units_sold = cnt; revenue = cnt * price; } This version and our original definition in previous have the same effect: When the constructor finishes, the data members will hold the same values. The difference is that the original version initializes its data members, whereas this version assigns values to the data members. How significant this distinction is depends on the type of the data member. We can often, but not always, ignore the distinction between whether a member is initialized or assigned. Members that are const or references must be initialized. #pragma once class test { public: //ok: test(int n) : a(n) {} //error: test(int n){ a = n; } private: const int a; }; By the time the body of the constructor begins executing, initialization is complete. Our only chance to initialize const or reference data members is in the constructor initializer. Another need notice in c++ is Order of Member Initialization, Members are initialized in the order in which they appear in the class definition: The first member is initialized first, then the next, and so on. The order in which initializers appear in the constructor initializer list does not change the order of initialization. The order of initialization often doesn’t matter. However, if one member is initialized in terms of another, then the order in which members are initialized is crucially important. We can set default arguments in constructors: test(int n = 10) : a(n) {} The new standard extends the use of constructor initializers to let us define so-called delegating constructors. A delegating constructor uses another constructor from its own class to perform its initialization. It is said to “delegate” some (or all) of its work to this other constructor. As an example, we’ll rewrite the Sales_data class to use delegating constructors as follows: class Sales_data { public: // nondelegating constructor initializes members from corresponding arguments Sales_data(std::string s, unsigned cnt, double price) : bookNo(s), units_sold(cnt), revenue(cnt* price) { } // remaining constructors all delegate to another constructor Sales_data() : Sales_data("", 0, 0) {} Sales_data(std::string s) : Sales_data(s, 0, 0) {} Sales_data(std::istream& is) : Sales_data() { read(is, *this); } // other members as before }; static Class MembersClasses sometimes need members that are associated with the class, rather than with individual objects of the class type. We say a member is associated with the class by adding the keyword static to its declaration. Like any other member, static members can be public or private. The type of a static data member can be const, reference, array, class type, and so forth. As an example, we’ll define a class to represent an account record at a bank: #ifndef Account_H #define Account_H #include <string> class Account { public: Account() = default; static double get_rate() { return rate; } static void set_rate(double r) { rate = r; } private: std::string user; double amount; static double rate; }; //define static member double Account::rate = 0.001; #endif The static members of a class exist outside any object. Objects do not contain data associated with static data members. Thus, each Account object will contain two data members—user and amount. There is only one rate object that will be shared by all the Account objects. How could we use a class static member? First, we can directly through the scope operator: double r; r = Account::get_rate(); // access a static member using the scope operator Even though static members are not part of the objects of its class, we can use an object, reference, or pointer of the class type to access a static member: Account s; double r; r = s.get_rate(); Member functions can use static members directly, without the scope operator. As with any other member function, we can define a static member function inside or outside of the class body. When we define a static member outside the class, we do not repeat the static keyword. The keyword appears only with the declaration inside the class body. Because static data members are not part of individual objects of the class type, they are not defined when we create objects of the class. As a result, they are not initialized by the class’ constructors. Moreover, in general, we may not initialize a static member inside the class. Instead, we must define and initialize each static data member outside the class body. Like any other object, a static data member may be defined only once.","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch2) C++ Basic Features - Type","slug":"Cpp_c02","date":"2021-12-02T13:52:45.000Z","updated":"2023-04-21T16:32:32.403Z","comments":true,"path":"2021/12/02/Cpp_c02/","link":"","permalink":"https://noahbishop.github.io/2021/12/02/Cpp_c02/","excerpt":"","text":"C++ Basic Features - TypeEvery widely used programming language provides a common set of features, which differ in detail from one language to another, this post briefly show the basic feature. Variables and basic typeType are fundamental to every programming language: the tell us what the data mean and what operations we can perform on those data. Primitive Built-in TypesC++ defines a set of Primitive Built-in Types that include arithmetic types and a special type called void, the arithmetic types represent character, integer, boolean values, floating-point numbers. The void type has no associated values and can be used in only a few circumstances, most commonly as the return type for functions that do not return a value. This table give the normally used types and details. type meaning size int integer 32 bits long long long integer 64 bits float single-precision-floating 32 bits double double-precision-floating 64 bits long double extended-precision-floating 128 bits char character 8 bits bool Boolean 8 bits #include <iostream> int main() { //you can use this statement check how many bytes for the type //change int to another type you want std::cout << sizeof(int) << std::endl; return 0; } The type of an object defines the data that an object might contain and what operations that object can perform. Among the operations that many types support is the ability to convert objects of the given type to other, related types. Type conversions happen automatically when we use an object of one type where an object of another type is expected. #include <iostream> int main() { //only b = 0 can't make b is false bool b = 10; // b is true //actually, true equal to 1 and false equal to 0 int i = b; // i has value 1 //the fractional part will be lose i = 3.14; // i has value 3 double pi = i; // pi has value 3.0 unsigned char c = -1; // assuming 8-bit chars, c has value 255 signed char c2 = 256; // assuming 8-bit chars, the value of c2 is undefined unsigned u = 10; int i = -42; //the type of sum result is int std::cout << i + i << std::endl; // prints -84 //the type of sum result is unsigned //the int tpye has been convert to a unsigned before add operation std::cout << u + i << std::endl; // if 32-bit ints, prints 4294967264 return 0; } VariablesA variable provides us with named storage that our programs can manipulate. Each variable in C++ has a type. The type determines the size and layout of the variable’s memory, the range of values that can be stored within that memory, and the set of operations that can be applied to the variable. C++ programmers tend to refer to variables as “variables” or “objects” interchangeably. The previous codes has explained how define a variable, but they are initialized, if you define a variable without an initializer, what that default value is depends on the type of the variable and may also depend on where the variable is defined. Some classes require that every object be explicitly initialized. The compiler will complain if we try to create an object of such a class with no initializer. The another notice is the difference between variable declarations and definitions. To allow programs to be written in logical parts, C++ supports what is commonly known as separate compilation. Separate compilation lets us split our programs into several files, each of which can be compiled independently. When we separate a program into multiple files, we need a way to share code across those files. A variable declaration specifies the type and name of a variable. A variable definition is a declaration. In addition to specifying the name and type, a definition also allocates storage and may provide the variable with an initial value. //extern keyword extern int i; // declares but does not define i int j; // declares and defines j extern double pi = 3.1416; // this is a error statement Identifiers in C++ can be composed of letters, digits, and the underscore character. The language imposes no limit on name length. Identifiers must begin with either a letter or an underscore. Identifiers are case-sensitive; upper- and lowercase letters are distinct, The standard also reserves a set of names for use in the standard library. The identifiers we define in our own programs may not contain two consecutive underscores, nor can an identifier begin with an underscore followed immediately by an uppercase letter. In addition, identifiers defined outside a function may not begin with an underscore. Conventions for Variable Names: An identifier should give some indication of its meaning Variable names normally are lowercase classes we define usually begin with an uppercase letter Identifiers with multiple words should visually distinguish each word, for example, student_loan or studentLoan A scope is a part of the program in which a name has a particular meaning. Most scopes in C++ are delimited by curly braces. The same name can refer to different entities in different scopes. Names are visible from the point where they are declared until the end of the scope in which the declaration appears. #include <iostream> //The name main is defined outside any curly braces //names defined outside a function has global scope int a = 0; int main() { int sum = 0; // sum values from 1 through 10 inclusive for (int val = 1; val <= 10; ++val) sum += val; // equivalent to sum = sum + val std::cout << "Sum of 1 to 10 inclusive is " << sum << std::endl; return 0; } Scopes can contain other scopes. The contained (or nested) scope is referred to as an inner scope, the containing scope is the outer scope. Once a name has been declared in a scope, that name can be used by scopes nested inside that scope. Names declared in the outer scope can also be redefined in an inner scope, this is a simple case. #include <iostream> // Program for illustration purposes only: It is bad style for a function // to use a global variable and also define a local variable with the same name int reused = 42; // reused has global scope int main() { int unique = 0; // unique has block scope // output #1: uses global reused; prints 42 0 std::cout << reused << " " << unique << std::endl; int reused = 0; // new, local object named reused hides global reused // output #2: uses local reused; prints 0 0 std::cout << reused << " " << unique << std::endl; // output #3: explicitly requests the global reused; prints 42 0 std::cout << ::reused << " " << unique << std::endl; return 0; } Compound TypesA compound type is a type that is defined in terms of another type. C++ has several compound types, two of which—references and pointers—we’ll cover in this chapter. A reference defines an alternative name for an object. A reference type “refers to” another type. We define a reference type by writing a declarator of the form &d, where d is the name being declared: Once initialized, a reference remains bound to its initial object. There is no way to rebind a reference to refer to a different object. Because there is no way to rebind a reference, references must be initialized. #include <iostream> int main() { int ival = 1024; int& refVal = ival; // refVal refers to (is another name for) ival refVal = 100; std::cout << ival << " " << refVal << std::endl; // will print 100 100 //error statement examples int &refVal2; // error: a reference must be initialized int &refVal4 = 10; // error: initializer must be an object double dval = 3.14; int &refVal5 = dval; // error: initializer must be an int object return 0; } A pointer is a compound type that “points to” another type. Like references, pointers are used for indirect access to other objects. Unlike a reference, a pointer is an object in its own right. Pointers can be assigned and copied; a single pointer can point to several different objects over its lifetime. Unlike a reference, a pointer need not be initialized at the time it is defined. Like other built-in types, pointers defined at block scope have undefined value if they are not initialized. the next case show how to use pointer: #include <iostream> int main() { // create a empty pointer int* ptr = nullptr; // ptrToN holds the address of n; ptrToN is a pointer to n //We get the address of an object by usin the address of operator //(the & operator) //note this is different from the reference int n = 10; int* ptrToN = &n; //print the address of n and value std::cout << "address: " << ptrToN << " value: " << *ptrToN << std::endl; //cahnge value of n by pointer, will print 100 *ptrToN = 100; std::cout << n << std::endl; //point to pointer int ival = 1024; int *pi = &ival; // pi points to an int int **ppi = &pi; // ppi points to a pointer to an int return 0; } (notice: Because references are not objects, they don’t have addresses. Hence, we may not define a pointer to a reference.) The type void* is a special pointer type that can hold the address of any object. Like any other pointer, a void pointer holds an address, but the type of the object at that address is unknown, so we cannot use a voidto operate on the object it addresses—we don’t know that object’s type, and the type determines what operations we can perform on the object. Generally, we use a void* pointer to deal with memory as memory, rather than using the pointer to access the object stored in that memory. const QualifierSometimes we want to define a variable whose value we know cannot be changed. We can make a variable unchangeable by defining the variable’s type as const. Because we can’t change the value of a const object after we create it, it must be initialized. const int n = 512; n = 1024; // error: attempt to write to const object const int k; //error: k is uninitialized const Sometimes we have a const variable that we want to share across multiple files, to share a const object among multiple files, you must define the variable as extern. As with any other object, we can bind a reference to an object of a const type. To do so we use a reference to const, which is a reference that refers to a const type. Unlike an ordinary reference, a reference to const cannot be used to change the object to which the reference is bound: const int ci = 1024; const int &r1 = ci; // ok: both reference and underlying object are const int &r2 = ci; // error: non const reference to a const object we noted that there are two exceptions to the rule that the type of a reference must match the type of the object to which it refers. //this code can be compilation double dval = 3.14; const int &ri = dval; //actually const int &ri = dval; will be think as this //const int temp = dval; // create a temporary const int from the double //const int &ri = temp; //const int &ri = temp; As with references, we can define pointers that point to either const or nonconst types. Like a reference to const, a pointer to const may not be used to change the object to which the pointer points. Next case show details. const double pi = 3.14; // pi is const; its value may not be changed double *ptr = &pi; // error: ptr is a plain pointer const double *cptr = &pi; // ok: cptr may point to a double that is const *cptr = 42; // error: cannot assign to *cptr double dval = 3.14; // dval is a double; its value can be changed cptr = &dval; // ok: but can't change dval through cptr *cptr = 5472; // error: cannot assign to *cptr Unlike references, pointers are objects. Hence, as with any other object type, we can have a pointer that is itself const. Like any other const object, a const pointer must be initialized, and once initialized, its value (i.e., the address that it holds) may not be changed. int errNumb = 0; int* const curErr = &errNumb; // curErr will always point to errNumb const double pi = 3.14159; const double* const pip = &pi; // pip is a const pointer to a const object We use the term top-level const to indicate that the pointer itself is a const. When a pointer can point to a const object, we refer to that const as a low-level const. Dealing with TypesA type alias is a name that is a synonym for another type. Type aliases let us simplify complicated type definitions, making those types easier to use. We can define a type alias in one of two ways. Traditionally, we use a typedef, the new standard introduced a second way to define a type alias, via an aliasdeclaration: typedef long long ll; // ll is a synonym for long long typedef long long *ptr; // ptr for long long* using ll = long long; // ll is a synonym for long long Under the new standard, we can let the compiler figure out the type for us by using the auto type specifier. Unlike type specifiers, such as double, that name a specific type, auto tells the compiler to deduce the type from the initializer. As with any other type specifier, we can define multiple variables using auto. Because a declaration can involve only a single base type, the initializers for all the variables in the declaration must have types that are consistent with each other: int n1 = 10, n2 = 20; double n3 = 3.5, n4 = 9.5; //type of item from the type returned by applying + to val1 and val2. auto s1 = n1 + n3; // s1's type is double auto s2 = n1 + n2; // s2;s type is int auto k1 = 10, k3 = 19; // ok auto k2 = 10, *k4 = &k2; // ok //error C++ 'auto' type is for this entity, but was previously implied to be int auto k5 = 10, k6 = 3.14; Sometimes we want to define a variable with a type that the compiler deduces from an expression but do not want to use that expression to initialize the variable. For such cases, the new standard introduced a second type specifier, decltype, which returns the type of its operand. const double m = 90.99, &r = m, * p = &m; decltype(m) ap = 184; // ap has type const int decltype(r) x = m; // x has type const int& and is bound to x decltype(m) y; // error: y is a const and must be initialized decltype(r) z; // error: z is a reference and must be initialized decltype(*p) c; // error: c is int& and must be initialized // decltype of a parenthesized variable is always a reference decltype((m)) d; // error: d is double& and must be initialized decltype(m) e; // ok: e is an (uninitialized) double Defining Our Own Data StructuresIn C++ we define our own data types by defining a class. The library types string, istream, and ostream are all defined as classes, we will tell how to defined a full class in next several chapters, first we create a class that does not support any operations. struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; Our class begins with the keyword struct, followed by the name of the class and a (possibly empty) class body. The class body is surrounded by curly braces and forms a new scope. The names defined inside the class must be unique within the class but can reuse names defined outside the class. The close curly that ends the class body must be followed by a semicolon. The semicolon is needed because we can define variables after the class body: struct Sales_data { /* ... */ } accum, trans, *salesptr; // equivalent, but better way to define these objects struct Sales_data { /* ... */ }; Sales_data accum, trans, *salesptr; Generally, we define a class in a separate header file, typically, classes are stored in headers whose name derives from the name of the class. Headers often need to use facilities from other headers. For example,because our Sales_data class has a string member, Sales_data.h must #include the string header. As we’ve seen, programs that use Sales_data also need to include the string header in order to use the bookNo member. As a result, programs that use Sales_data will include the string header twice: once directly and once as a side effect of including Sales_data.h. Because a header might be included more than once, we need to write our headers in a way that is safe even if the header is included multiple times. The most common technique for making it safe to include a header multiple times relies on the preprocessor. The preprocessor—which C++ inherits from C—is a program that runs before the compiler and changes the source text of our programs. Our programs already rely on one preprocessor facility, #include. When the preprocessor sees a #include, it replaces the #include with the contents of the specified header. C++ programs also use the preprocessor to define header guards. Header guards rely on preprocessor variables. Preprocessor variables have one of two possible states: defined or not defined. The #define directive takes a name and defines that name as a preprocessor variable. There are two other directives that test whether a given preprocessor variable has or has not been defined: #ifdef is true if the variable has been defined, and #ifndef is true if the variable has not been defined. If the test is true, then everything following the #ifdef or #ifndef is processed up to the matching #endif. //Sales_data.h #ifndef SALES_DATA_H #define SALES_DATA_H #include <string> struct Sales_data { std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; #endif The first time Sales_data.h is included, the #ifndef test will succeed. The preprocessor will process the lines following #ifndef up to the #endif. As a result, the preprocessor variable SALES_DATA_H will be defined and the contents of Sales_data.h will be copied into our program. If we include Sales_data.h later on in the same file, the #ifndef directive will be false. The lines between it and the #endif directive will be ignored. this is a simple case to statistics sales of book: #include <iostream> #include <string> #include "Sales_data.h" int main() { Sales_data book1, book2; double price = 0; // read the first transactions: ISBN, number of books sold, price per book std::cin >> book1.bookNo >> book1.units_sold >> price; book1.revenue = price * book1.units_sold; // read the second transactions: ISBN, number of books sold, price per book std::cin >> book2.bookNo >> book2.units_sold >> price; book2.revenue = price * book2.units_sold; //add operation if (book1.bookNo == book2.bookNo) { unsigned totalCnt = book1.units_sold + book2.units_sold; double totalRevenue = book1.revenue + book2.revenue; // print: ISBN, total sold, total revenue, average price per book std::cout << book1.bookNo << " " << totalCnt << " " << totalRevenue << " "; if (totalCnt != 0) std::cout << totalRevenue / totalCnt << std::endl; else std::cout << "(no sales)" << std::endl; return 0; } else { std::cout << "there are different books" << std::endl; return -1; } } Strings, Vectors, and ArraysIn addition to the built-in types covered in previous, C++ defines a rich library of abstract data types. Among the most important library types are string, which supports variable-length character strings, and vector, which defines variable-size collections. Associated with string and vector are companion types known as iterators, which are used to access the characters in a string or the elements in a vector. Before beginning our exploration of the library types, we’ll look at a mechanism for simplifying access to the names defined in the library. Namespace using DeclarationsUp to now, our programs have explicitly indicated that each library name we use is in the std namespace. For example, to read from the standard input, we write std::cin. std::cin says that we want to use the name cin from the namespace std. there are easier ways to use namespace members. The safest way is a using declaration. #include <iostream> // using declaration; when we use the name cin, we get the one from the namespacestd using std::cin; int main() { int i; cin >> i; // ok: cin is a synonym for std::cin cout << i; // error: no using declaration; we must use the full name std::cout << i; // ok: explicitly use cout from namepsace std return 0; } Note “Headers Should Not Include using Declarations”, The reason is that the contents of a header are copied into the including program’s text. If a header has a using declaration, then every program that includes that header gets that same using declaration. Library string TypeEach class defines how objects of its type can be initialized. A class may define many different ways to initialize objects of its type. examples: #include <iostream> #include <string> using std::string; int main() { string s1; // default initialization; s1 is the empty string string s2 = s1; // s2 is a copy of s1 string s3 = "hello";// s3 is a copy of the string literal string s4(10, 'A'); // s4 is AAAAAAAAAA string s5(s1); // s5 is a copy of s1 string s6("hello"); // s6 is a copy of the string literal return 0; } When we initialize a variable using =, we are asking the compiler to copy initialize the object by copying the initializer on the right-hand side into the object being created. Otherwise, when we omit the =, we use direct initialization. string s5 = "hiya"; // copy initialization string s6("hiya"); // direct initialization string s7(10, 'c'); // direct initialization; s7 is cccccccccc Along with defining how objects are created and initialized, a class also defines the operations that objects of the class type can perform. The string input operator reads and discards any leading whitespace (e.g., spaces, newlines, tabs). It then reads characters until the next whitespace character is encountered. which mean when we read a string containing whitespace will cause error. we can use the getline function instead of the >> operator. The getline function takes an input stream and a string. //input operations and output operations string str; //initlize a empty string cin >> str; //read from istream cout << str << endl; //write to output getline(cin, str); //read a line cout << str << endl; //write to output Sometimes we want get the information of string, such as if empty and the size of string, The empty function does what one would expect: It returns a bool indicating whether the string is empty. The size member returns the length of a string. It might be logical to expect that size returns an int or unsigned. Instead, size returns a string::size_type value.The string class—and most other library types—defines several companion types.These companion types make it possible to use the library types in a machine independent manner. The type size_type is one of these companion types. To use the size_type defined by string, we use the scope operator to say that the name size_type is defined in the string class. Admittedly, it can be tedious to type string::size_type. Under the new standard, we can ask the compiler to provide the appropriate type by using auto or decltype auto emp = str.empty(); //check empty auto len = str.size(); //get size The string class defines several operators that compare strings. These operators work by comparing the characters of the strings. The comparisons are case sensitive, uppercase and lowercase versions of a letter are different characters. // all compare operation cout << (str1 == str2) << endl; cout << (str1 != str2) << endl; cout << (str1 >= str2) << endl; cout << (str1 <= str2) << endl; cout << (str1 > str2) << endl; cout << (str1 < str2) << endl; //Adding Two strings string s1 = "hello, ", s2 = "world\\n"; string s3 = s1 + s2; // s3 is hello, world\\n s1 += s2; // equivalent to s1 = s1 + s2 //Adding Literals strings and strings string s1 = "hello", s2 = "world"; string s3 = s1 + ", " + s2 + '\\n'; // ok string s4 = s1 + ", "; // ok: adding a string and a literal string s5 = "hello" + ", "; // error: no string operand string s6 = s1 + ", " + "world"; // ok: each + has a string operand string s7 = "hello" + ", " + s2; // error: can't add string literals For historical reasons, and for compatibility with C, string literals are not standard library strings. It is important to remember that these types differ when you use string literals and library strings. Often we need to deal with the individual characters in a string. We might want to check to see whether a string contains any whitespace, or to change the characters to lowercase, or to see whether a given character is present, and so on. The one part of processing characters is knowing and/or changing the characteristics of a character. This part of the job is handled by a set of library functions, described in next Table. These functions are defined in the cctype header. function name meanning isalnum Check if character is alphanumeric isalpha Check if character is alphabetic isblank Check if character is blank iscntrl Check if character is a control character isdigit Check if character is decimal digit isgraph Check if character has graphical representation islower Check if character is lowercase letter isprint Check if character is printable ispunct Check if character is a punctuation character isspace Check if character is a white-space isupper Check if character is uppercase letter isxdigit Check if character is hexadecimal digit tolower Convert uppercase letter to lowercase toupper Convert lowercase letter to uppercase Another of this kind of processing involves how we gain access to the characters themselves. Sometimes we need to process every character. Other times we need to process only a specific character, or we can stop processing once some condition is met. If we want to do something to every character in a string, by far the best approach is to use a statement introduced by the new standard: the range for statement. string str = "hello!!!"; for (auto c : str) { // for every char in str cout << c << endl; // print the current character followed by a newline } //count the number of punctuation decltype(str.size()) punct = 0; for (auto c : str) { if (ispunct(c)){ // if the character is punctuation ++punct; } } cout << punct << endl; If we want to change the value of the characters in a string, we must define the loop variable as a reference type, Remember that a reference is just another name for a given object. string str = "hello!!!"; for (auto &c : str) { c = toupper(c); // c is a reference, so the assignment changes the char in str } cout << str << endl; A range for works well when we need to process every character. However, sometimes we need to access only a single character or to access characters until some condition is reached. There are two ways to access individual characters in a string: We can use a subscript or an iterator. The subscript operator (the [ ] operator) takes a string::size_type value that denotes the position of the character we want to access. Subscripts for strings start at zero, so str[0] is the first character, and the last character is in str[str.size() - 1]. we can rewrite previous as this: for (decltype(str.size()) i = 0; i < str.size(); i++) { str[i] = toupper(str[i]); } cout << str << endl; When we use a subscript, we must ensure that the subscript is in range. That is, the subscript must be >= 0 and < the size() of the string. One way to simplify code that uses subscripts is always to use a variable of type string::size_type as the subscript. Because that type is unsigned, we ensure that the subscript cannot be less than zero. Library vector TypeA vector is a collection of objects, all of which have the same type. Every object in the collection has an associated index, which gives access to that object. A vector is often referred to as a container because it “contains” other objects. A vector is a class template. C++ has both class and function templates. Writing a template requires a fairly deep understanding of C++. Indeed, we won’t see how to create our own templates now! Fortunately, we can use templates without knowing how to write them. For a class template, we specify which class to instantiate by supplying additional information, the nature of which depends on the template. How we specify the information is always the same: We supply it inside a pair of angle brackets following the template’s name. Note vector is a template, not a type. Types generated from vector must include the element type, for example, vector<int>. #include <string> #include <vector> using std::string; using std::vector; int main() { vector<int> ivec; //ivec holds objects of type int vector<vector<string>> strvec;//strvec holds objects of type vector<string> return 0; } As with any class type, the vector template controls how we define and initialize vectors. Next code lists the most common ways to define vectors. #include <vector> using std::vector; int main() { vector<int> ivec1; //default initlization, ivec1 is empty vector<int> ivec2 = ivec1; //ivec2 is copy of ivec1 vector<int> ivec3(ivec1); //ivec3 is copy of ivec1 vector<int> ivec4(5, 1); //ivec4 have 5 element with value 1 vector<int> ivec5(5); //ivec5 have 5 element with value initliazated vector<int> ivec6{ 1,2,3,4,5 }; //ivec6 have 1,2,3,4,5 vector<int> ivec7 = { 1,2,3,4,5 };//ivec7 have 1,2,3,4,5 return 0; } When we use parentheses, we are saying that the values we supply are to be used to construct the object. Thus, ivec4 and ivec5 use their initializers to determine the vector’s size, and its size and element values, respectively. When we use curly braces, {…}, we’re saying that, if possible, we want to list initialize the object. That is, if there is a way to use the values inside the curly braces as a list of element initializers, the class will do so. Only if it is not possible to list initialize the object will the other ways to initialize the object be considered. vector<string> ivec8{10, "hi"}; // ivec8 has ten elements with value "hi" vector<string> ivec9{10}; // ivec9 has ten default-initialized elements Directly initializing the element of a vector just feasible only if we have a small number of known initial values or we want make a copy of a vector. More commonly, we use the push_back method add elements when running. The push_back operation takes a value and “pushes” that value as a new lastelement onto the “back” of the vector. This code will add 0 to 99 to a vector in sequence: vector<int> ivec1; //default initlization, ivec1 is empty for (int i = 0; i < 100; i++){ ivec1.push_back(i); // add to ivec1 } The standard requires that vector implementations can efficiently add elements at run time. Because vectors grow efficiently, it is often unnecessary and can result in poorer performance to define a vector of a specific size. Note we cannot use a range for if the body of the loop adds elements to the vector. The body of a range for must not change the size of the sequence over which it is iterating. In addition to push_back, vector provides only a few other operations, most of which are similar to the corresponding operations on strings. we can get full operations in here. We access the elements of a vector the same way that we access the characters in a string: vector<int> v{1,2,3,4,5,6,7,8,9}; for (auto &i : v) // for each element in v (note: i is a reference) i *= i; // square the element value for (auto i : v) // for each element in v cout << i << " "; // print the element cout << endl; The empty and size members behave as do the corresponding string members: empty returns a bool indicating whether the vector has any elements, and size returns the number of elements in the vector. The size member returns a value of the size_type defined by the corresponding vector type. //note vector is only a template vector<int>::size_type // ok vector::size_type // error Programmers new to C++ sometimes think that subscripting a vector adds elements; it does not. The following code intends to add ten elements to ivec: vector<int> ivec; // empty vector for (decltype(ivec.size()) ix = 0; ix != 10; ++ix) ivec[ix] = ix; // disaster: ivec has no elements However, it is in error: ivec is an empty vector; there are no elements to subscript! As we’ve seen, the right way to write this loop is to use push_back like previous. Introducing IteratorsAlthough we can use subscripts to access the characters of a string or the elements in a vector, there is a more general mechanism—known as iterators—that we can use for the same purpose. All of the library containers have iterators, but only a few of them support the subscript operator. Like pointers, iterators give us indirect access to an object. We can use an iterator to fetch an element and iterators have operations to move from one element to another. As with pointers, an iterator may be valid or invalid. A valid iterator either denotes an element or denotes a position one past the last element in a container. All other iterator values are invalid. Unlike pointers, we do not use the address-of operator to obtain an iterator. Instead, types that have iterators have members that return iterators. This code show getting vector iterators. vector<int> v; //In general, we do not care about the precise type that an iterator has. //In this example, we used auto to define b and e auto b = v.begin(), e = v.end(); The begin member returns an iterator that denotes the first element (or first character), The iterator returned by end is an iterator positioned “one past the end” of the associated container (or string). This iterator denotes a nonexistent element “off the end” of the container. If the container is empty, beginreturns the same iterator as the one returned by end. Iterators support only a few operations, which are listed in next code: #include <iostream> #include <string> #include <vector> #include "Sales_data.h" using std::cout; using std::cin; using std::endl; using std::string; using std::vector; int main() { vector<int> v{ 1,2,3,4,5,6,7,8,9 }; //b++: Increments b to refer next element //b--: Decrements b to refer previous element //b != v.end(): Compare two iterator for inequality for (auto b = v.begin(); b != v.end(); b++){ //*b get the reference to the element donated by b *b *= *b; } Sales_data k; k.bookNo = "0-321-71411-3"; vector<Sales_data> sale = {k}; auto sb = sale.begin(), se = sale.end(); //equal to (*sb).bookNo will print 0-321-71411-3 cout << sb->bookNo << endl; return 0; } Like pointer, there ere also has const iterator, when you define a const container(such as vector) or string, the begin and end members are const iterator. You also can use cbigin or cend to get a const iterator when you don not want change element. Except previous operation, they are some operations for special container. Iterators for string and vector support additional operations that can move an iterator multiple elements at a time. They also support all the relational operators. These operations, which are often referred to as iterator arithmetic, are described in the next Table. operation meaning iter + n iter donate to next nth element iter - n iter donate to front nth element iter += n iter donate to next nth element iter -= n iter donate to front nth element <, >, <=, >= compare ArraysAn array is a data structure that is similar to the library vector type, but size of a array is fixed, which mean we can not add new elements. Sometimes they offer better run-time performance for specialized applications. Arrays are a compound type, An array declarator has the form a[d], where a is the name being defined and d is the dimension of the array. the next code show how initial a array: int p = 10; int a[10]; //array of ten ints int* b[10]; //array of ten points to int int d[p]; //error: p is not a constexpr int c[] = { 1,2,3 }; //array of three ints, 1 and 2 and 3 When we define an array, we must specify a type for the array. We cannot use auto to deduce the type from a list of initializers. As with vector, arrays hold objects. Thus, there are no arrays of references. Character arrays have an additional form of initialization. We can initialize such arrays from a string literal. When we use this form of initialization, it is important to remember that string literals end with a null character. In previous code, we see int* b[10] mean array of ten points to int, so how can we make a pointer point to a array? The next show: int a[10]; //array of ten ints int(*p)[10] = &a; //a pointer point to a int(&r)[10] = a; //a reference refer to a //By default, type modifiers bind right to left. As with the library vector and string types, we can use a range for or the subscript operator to access elements of an array. As usual, the indices start at 0. When we use a variable to subscript an array, we normally should define that variable to have type size_t. size_t is a machine-specific unsigned type that is guaranteed to be large enough to hold the size of any object in memory. In C++ pointers and arrays are closely intertwined. In particular, as we’ll see, when we use an array, the compiler ordinarily converts the array to a pointer. #include <iostream> int main() { int f = 10; int a[10]; //array of ten ints for (auto &i : a) { i = f--; } //p is a pointer point to the first element of a auto p = a; //will print 10 std::cout << *p << std::endl; return 0; } Array is not a class, so it is not has member donated to begin and end, c++ provide a way to get pointer to fist element and last element. We will use this function in next code. Many C++ programs predate the standard library and do not use the string and vector types. Moreover, many C++ programs interface to programs written in C or other languages that cannot use the C++ library. Hence, programs written in modern C++ may have to interface to code that uses arrays and/or C-style character strings. The C++ library offers facilities to make the interface easier to manage. #include <iostream> #include <string> #include <vector> using std::string; using std::vector; using std::begin; using std::end; int main() { char str[] = "Hello"; string s(str); //c string to std string const char* cs = s.c_str(); // std string to c string, must need const int int_arr[] = { 0, 1, 2, 3, 4, 5 }; // ivec has six elements; each is a copy of the corresponding element in int_arr vector<int> ivec(begin(int_arr), end(int_arr)); return 0; }","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]},{"title":"(ch1) Getting Started C++","slug":"Cpp_c01","date":"2021-12-01T13:52:45.000Z","updated":"2023-04-21T16:00:56.416Z","comments":true,"path":"2021/12/01/Cpp_c01/","link":"","permalink":"https://noahbishop.github.io/2021/12/01/Cpp_c01/","excerpt":"","text":"Getting StartedThis chapter introduces most of the basic elements of C++: types, variables,expressions, statements, and functions. I write this post in 2021, today there are many powerful tools can help you write program more efficiently and quickly, I recommend you use vs2019 and create a “Linux project” and running in a virtual machine. you can simply create a virtual machine by VMware. this post is based on c plus plus primer. write a simple c++ programEvery c++ program contains one or more functions, one of which must be named main. The operating system runs a C++ program by calling main, this is a simple c++ program. #include<cstdio> int main() { printf("hello world!\\n"); return 0; } A function definition has four elements: a return type, a function name, a (possibly empty) parameter list enclosed in parentheses, and a function body. In previous example, the int is return type, main is function name, and don’t have parameter. The function body is a block of statements starting with an open curly brace and ending with a close curly. Note the semicolon at the end of statement, it can lead to compiler error messages when forgotten. Input and outputThe c++ don’t define any statement to do input and output, but c++ include a extensive standard library that provide input and output. In previous case, we print the string “hello world” by use the “printf” function that define in cstdio standard library. Actually, you are recommend use the iostream standard library, the cstdio is from the stdio.h in c language. Fundamental to iostream are istream and ostream, which represent input and output streams. The library defines four IO objects. name in where referred cin istream standard input cout ostream standard output cerr ostream standard error clog ostream general information about the execution of the program This is a sample case, read two numbers form standard input and print the sum by standard output. #include <iostream> int main() { int num1, num2; std::cout << "Enter two numbers:" << std::endl; std::cin >> num1 >> num2; std::cout << "the sum is:" << num1 + num2 << std::endl; return 0; } //running //Enter two numbers: //10 20 //the sum is:30 The first line in the previous case is #include <iostream> tell the compiler that wo want use the iostream library, we called it header in c++ program. And we notice that the program use the std::cin and std::cout rather than cin and cout, the profix std:: mean the cin and cout are defined in the namespace called std. The namespace allow us to use function that has same name in a program. commentsComments help human reading program, the often to summarize a algorithm and explain what the variable’s mean, the compiler ignore the comments, so they need be identified by special way. //this is a comment /* you can also write conmment is this way */ /*Comment Pairs Do Not Nest /*Comment Pairs Do Not Nest, the end of a comment is */ compler think this line is a statement, because the comments is end*/ Flow of ControlStatement normally execute sequentially, the first statement at main block execute first, follow by second, the most important control ways in program are while loop, for loop and if branch. the following program add all even numbers between 0 to 20. #include <iostream> int main() { /* * this is a simple program that * add all even numbers between 0 to 20 */ int n = 0, sum = 0; // while loop end when n greater than 20 while (n <= 20) { // if n is a even number if (n % 2 == 0) { sum += n; } n++; } std::cout << "the sum of even numbers between 0 to 20 is " << sum << std::endl; sum = 0; // for loop end when i greater than 20 for (int i = 0; i <= 20; i++) { // if i is a even number if (i % 2 == 0) { sum += i; } } std::cout << "the sum of even numbers between 0 to 20 is " << sum << std::endl; return 0; } Introducing Classesone of the most difference between c language and c++ language is the c++ has classes. In c++, we define our data structure by define a classes, a class defines a type along with many operations that are related to this type. We will discuss more details in the next post.","categories":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"C/C++ basic","slug":"C-C-basic","permalink":"https://noahbishop.github.io/tags/C-C-basic/"}],"keywords":[{"name":"技术","slug":"技术","permalink":"https://noahbishop.github.io/categories/%E6%8A%80%E6%9C%AF/"}]}]}