Skip to main content

Strings and IO Revisited

 

Strings and IO Revisited

- C++ introduces new classes for strings and for performing IO while C approaches are still valid. They provide a more object-oriented approach than C counterparts. 

Strings

- C++ provides a string class. And we can still define and manipulate C-style strings via a char* whenever it is appropriate.

- The string class in C++ encapsulates the sequence of bytes that form a string into an object that provides a variety of methods and operators to operate on that type. 
e.g.
strings have length(), += operators to concat, [] to index. [] returns the type: char &. (If the string is non-const type, else it returns const char &)

- The string class also provides constructors, including one that takes a const char *. It allows creation of C++ strings from C strings, including string literals.
e.g.
std::string s("Hello World");
can create a C++ string object

Cautionary Example

recursion e.g.
#include <string>
std::string reverse(const std::string & s, std::string & ans, size_t ind) {
    if (ind == s.length()) {
        return ans;
    }
    ans[ind] = s[s.length() - ind - 1];
    return reverse(s, ans, ind + 1);
}

std::string reverse (std::string s) {
    std::string ans(s.length(), '\0');
    return reverse(s, ans, 0);
}

- To be efficient, we should think about passing the reference instead of value, as well as where temporary objects are created, and how they might be eliminated. 


Output

- In C, we use fprintf or printf to print output.

- An OO approach lets a class define how to print itself with a method encapsulated inside of it. 

- In C++, the fundamental type for output operation is std::ostream, and the << operator is overloaded to work with it as the left-hand operand with a variety of possible types for its right-hand operand.

- When using the << operator to work with std::ostreams, it is called the "stream insertion operator", otherwise, it is typically called the "left shift operator", as it performs bit shift left for integers.

- C++ introduces std::cout instead of stdout. It is a std::ostream. Likewise, there is std::cerr, which is analogous to stderr.
e.g.
#include <iostearm>
#include <cstdlib>

int main(void) {
    std::cout << "Hello World\n";
    return EXIT_SUCCESS;
}

- In this example, <iostream> includes std::cout, which is std::ostream type and the built-in overloading of <<.

- It also includes <cstdlib> for the definition of EXIT_SUCCESS.
e.g.
#include <iostearm>
#include <cstdlib>

int main(void) {
    for (int i = 0; i < 10; i++) {
        std::cout << "i = "<< i << "\n";
        std::cout << "and i / (i + 1) = "<< (i / (double) (i + 1)) << " \n ";
    }
}

These two statements are like printf("i = %d\n", i);
printf("and i / (i + 1) = %f\n", i/(double)(i+1));

- The << operator applied to an ostream and an int is a different function from the << operator applied to an ostream and a double;

Return Type of Stream Insertion

- For integers, << will first convert these to a sequence of characters, then write those characters to the underlying ostream. And after that, it return the original stream as the return result of the operator.
i.e.
std::ostream & operator <<(std::ostream & stream, int num) {
    char asString[16];
    snprintf(asString, 16, "%d", num);
    stream.write(asString, strlen(asString));
    return stream;
}

- If we write overload for <<  for our own types, having the return value ostream & will enable us to chain multiple of our << operators and mix them with other overloadings in a single statement.

- However, if we make << return void. we will not be able to chain them together.

Writing Stream Insertion for Our Own Classes

- In an ideal world, we would write the overloading of the << operator inside of a class that we were writing. Such a design would fit the OO design principle of encapsulation. 

- To print something, we need it to be the left operand. Therefore, we must write a method outside of the class that looks like this:
std::ostream & operator<<(std::ostream & stream, const MyClass & data) {}

- But since this method is now outside the class and may require access to the private internals of the class to print out an instance

- C++ resolves this conundrum by allowing a class to declare functions or class as its friend. When a class declares a function or a class as its friend, that function or class is allowed access to the private elements of the declaring class.
e.g.
class MyClass {
    friend std::ostream & operator<<(std::ostream & stream,
                                                            const MyClass & data)
}

- As overloaded functions are different from each other, a function with the same name but different parameter types as a friend would not itself be a friend.

- Declaring something as a friend inherently violates the core principles of OO design: it lets something outside of the class alter its private internal state.

Controlling Formatting

- The stream has internal state that keeps track of the current formatting specifications. The current format state is altered by either inserting special values (called manipulators) with the << operator or by calling methods on the stream.

- To change the base in which integers are printed, the first approach is to use: std::hex, std::dec, or std::oct accordingly. 
e.g.
int x = 42;
std::cout << x <<"\n";
std::cout << std::hex << x << "\n";
std::cout << std::oct << x << "\n";
std::cout << std::dec << x << "\n";

- Here we print the integer x four times. The first time converts the number in whatever base the cout stream was previously left in. Assuming we have not modified its base, it is in decimal, as the C++ library is guaranteed to start the stream in decimal mode.

- The next line first changes the mode of the stream to hexdecimal then print x.
etc.

- It's a good practice to put a stream back into decimal mode when we are done printing things in other modes.

- Other types of formatting are controlled via methods in the stream class.  
e.g.
std::cout.width(5)
would set the field width of cout to five, meaning that an output conversion that uses field width will print at least five characters. What extra characters get printed is controlled by calling the fill method and passing in the desired character.

- Similarly, the floating point precision can be controlled with the precision method.

Input

- In addition to introducing new ways to perform output, C++ also introduces new ways to perform input.

- We can use the stream extraction operator >>.
e.g.
int x;
std::cin >> x;
to read an integer from standard input. 

- As with output streams, input streams have a base they work in, which is by default decimal. Unless we have changed the base of std::cin (e.g. with std::cin >> hex), the integer will be converted from the text typed on standard input as a decimal number.

- If we want to overload the stream extraction, we should take a reference to the input stream and a reference to the type of data we want to read. (This reference should not be const since we are going to alter the data in it).
e.g.
std::istream & operator>>(std::istream & stream, MyClass & data) {
    return stream;
}

- It is the same that we can make this overloaded operator a friend of my class so that it can modify the internal data directly in ways that may not be possible through the external interface.

Errors

e.g.

int x;
std::cin >> x;

- What if we execute this code, but the user enters xyz?

- The design of C++ operator precludes using the return value or passing another argument to report the error. Instead, the input stream object contains an error state internally. When an operation produces an error, it sets the stream's error state. This error state persists, causing future operation to fail, until it is explicitly cleared. 

- When an error occurs, the input that could not be converted is left unread, so future read operations can try to read it.
e.g.
int x;
std::cin >> x;
if (!std::cin.good()) {
    std::cin.clear();
    std::string badinput;
    std::cin >> badinput;
    if(!std::cin.good()){
        std::cerr << "Cannot read anything from cin!\n";
    } else {
    std::cerr << "Oops! " << badinput << " wasn't an int!\n"
    }
}

- Here, clear clears the error state.

- C++'s input streams actually distinguish between three types of failures: 
    1) end of file (which can be checked with the .eof() method) 
    2) logical errors on the operation (such as an inability to perform the proper conversion, which can be checked with the .fail() method) 
    3) errors with the underlying I/O operation (which can be checked with the .bad() method if .fail() returned false)

- C++'s output streams can also experience errors and can have similar methods applied to them as needed.

- If we use >> operator to read a string, it will only read one "word", not a line. i.e., it will stop at a whitespace for what it converts into the string.

- If we want to read an entire line, we can use std::getline

Other Streams

- C++ has classes that work with files

Files

- In C++, files are manipulated using the std::ifstream for reading, and std::ofstream for writing. They have default constructor which create an object with no file associated with it. They also have constructors that takes a const char * specifying the file name. 

- An open file can be closed with the close method.

- To use any of these method, we should include <fstream>

- When using these classes, data is written to the file with << (stream insertion operator) and read from the file with >> (stream extraction operator).

- ofstream is a special type of ostream and thus is compatible with things that expect pointers or references to ostreams. Therefore, we can use both << and >> on it.

String Streams

- If we don't want to print the output to a file or read from a file. Instead, we want to perform this functionality in a way that we can get the resulting string into a variable or extract the fields from a string variable, we can use std::stringstream class.

- We can use << operator to let the string accumulate the text that would be printed  using a normal stream. 

-Whenever we are done building the string up, we can use the .str() method to get a std::string with all the text we have inserted into the stream.

- We can construct the string stream with the constructor that takes a string, passing in the string we want to pull apart, then we use >> to extract fields into variables of appropriate types. And of course, as with other streams, the extraction could fail. Therefore, we must check for errors.

- We can insert text into a string stream with << then extract it back out with >>.

summary& understanding  after reading <<All of Programming>> Chapter 16

Comments

Popular posts from this blog

Templates

  Template - Polymorphism is the ability of the same code to operate on different types. This ability to operate on multiple types reduces code duplication by allowing the same piece of code to be reused across the different types it can operate on. - Polymorphism comes in a variety of forms. What we are interested in at the moment is parametric polymorphism, meaning that we can write our code so that it is parameterized over what type it operates on.  -That is, we want to declare a type parameter T and replace int with T in the above code. -Then, when we want to call the function, we can specify the type for T and get the function we desire. C++ provides parametric polymorphism through templates. Templated Functions - We can write a templated function by using the keyword template followed by the template parameters in angle brackets (<>). - Unlike function parameters, template parameters may be types, which are specified with typename where the type of the parameter wo...

前端 优化代码体积

当我使用npm run build的时候,项目构建了很久。所以考虑用create-react-app网站下面的工具来缩小代码体积  Analyzing the Bundle Size https://create-react-app.dev/docs/analyzing-the-bundle-size 更改完成后 npm run build npm run analyze 可以看到以下的图片: 其中main.js有1.57mb 然后起服务 serve -s build -l 8000 进入到首页之后,打开network,查看js,发现main.js有500kb。这个500kb是已经用gzip压缩过了,但是却还有这么大。500*3=1500说明源文件有1.5mb左右 其中, antd占了25%, recharts占了13%, react-dom占了7.6%,dnd-kit占了2.8% 其中recharts用于统计页面,dnd-kit用于拖拽排序-编辑器页面。 所以在加载首页的时候,先不加载编辑页面和统计页面的js的话,体积会小很多。 路由懒加载 因为项目中,体积占比最大的是Edit和Stat页面(编辑和统计页面),所以考虑使用路由懒加载,拆分bundle,优化首页体积 router文件中,之前: import Edit from "../pages/question/Edit" ; import Stat from "../pages/question/Stat" ; 现在: const Edit = lazy (() => import ( "../pages/question/Edit" )); const Stat = lazy (() => import ( "../pages/question/Stat" )); 为了让生成的文件更加可读,可以改成下面这样: const Edit = lazy ( () => import ( /* webpackChunkName: "editPage" */ "../pages/question/Edit" ) ); const Stat ...

useMemo的使用场景

 useMemo是用来缓存 计算属性 的。 计算属性是函数的返回值,或者说那些以返回一个值为目标的函数。 有些函数会需要我们手动去点击,有些函数是直接在渲染的时候就执行,在DOM区域被当作属性值一样去使用。后者被称为计算属性。 e.g. const Component = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 = () => { console . log ( "我需要手动调用,你不点击我不执行" ); setParams1 (( val ) => val + 1 ); }; //这种被称为计算属性,不需要手动调用,在渲染阶段就会执行的。 const computedFun2 = () => { console . log ( "我又执行计算了" ); return params2 ; }; return ( < div onClick = { handleFun1 } > //每次重新渲染的时候我就会执行 computed: { computedFun2 () } </ div > ); }; 上面的代码中,在每次点击div的时候,因为setParams1的缘故,导致params1改变,整个组件都需要重新渲染,也导致comptedFunc2()也需要重新计算。如果computedFunc2的计算量很大,这时候重新计算会比较浪费。 可以使用useMemo: const Com = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 ...