How to understand the complex types of C and C ++ statements (transfer)

Source: Internet
Author: User

Original translated from: http://blog.csdn.net/angle_birds/article/details/8991802

Keyword: right-left rule

Right left rule: First, start from the parentheses in the innermost part, then look to the right, and then look to the left. When parentheses are encountered, the reading direction should be dropped. Once everything in the parentheses is parsed, the parentheses appear. Repeat this process until the entire declaration is parsed.

 

Original article:

 

Girlrong, author of this article, is the C language Moderator of Netease Guangzhou community. This article is selected in the essence area. Very good, do not dare to exclusive! It is said that she is willing to help others, sincere and sincere, and quite popular with netizens. It is a pity that the world has been retired. When I recently studied C language, I learned some of my experiences and views from my predecessors.


Have you ever encountered a variable declaration like int * (* FP1) (INT) [10]; that puzzles you?

This article will show you how to understand this complex C/C ++ statement step by step.

We will start with a simple declaration that we can encounter every day, and then gradually add the const modifier and typedef, as well as the function pointer, finally, we will introduce a "right-left rule" that allows you to accurately understand any C/C ++ statement ".

It should be emphasized that the complex C/C ++ statements are not a good programming style. Here I will only teach you how to understand these statements. Note: to ensure that the Code and related comments can be displayed on the same line, read this article on a display with a resolution of at least X.

Let's start with a very simple example:

Int N;

This should be understood as "declare N as an int" (N is an int type variable ). Next, let's take a look at the pointer variables as follows:

Int * P;

This should be understood as "declare P as an int *" (P is an int * type variable), or P is a pointer to an int type variable. I would like to discuss it here: I think it is best to write * (OR &) before it is near the variable when declaring a pointer (or reference) type variable, instead of following the basic type. This avoids some misunderstandings, such:

Let's look at an example of a pointer:

Char ** argv;

Theoretically, there is no limit on the level of the pointer. You can define the pointer of a floating point type variable. Let's look at the following statement:

Int rollnum [30] [4];

INT (* P) [4] = rollnum;

Int * Q [5];

Here, P is declared as a pointer to a 4-element (INT type) array, and Q is declared as an array containing 5 elements (INT type pointer. In addition, we can also mix and use * and & in the same declaration, as follows:

Int ** P1;

// P1 is a pointer to an int.

Int * & P2;

// P2 is a reference to a pointer to an int.

Int & * P3;

// Error: pointer to a reference is illegal.

Int & P4;

// Error: Reference to a reference is illegal.

Note: P1 is a pointer of the int type; P2 is a reference of the int type; P3 is a pointer of the int type reference (invalid !); P4 is a reference of the int type reference (invalid !).

 

Const Modifier

When you want to prevent a variable from being changed, the const keyword may be used. When you add a const modifier to a variable, you usually need to initialize it, because you will not have the opportunity to change it any time in the future. For example:

Const int n = 5;

Int const M = 10;

The preceding two variables N and m are of the same type -- both are const int (integer constant ). According to the C ++ standard, the const keyword is equivalent before the type or variable name. I personally prefer the first declaration method because it highlights the role of the const modifier. Const and pointer are confusing. For example, let's take a look at the p and q statements in the pipeline:

Const int * P;

Int const * q;

Which of them represents the const int type pointer (const directly modifies INT), and which one represents the int type const pointer (const directly modifies the pointer )? In fact, both p and q are declared as const int type pointers. The const pointer of the int type should be declared as follows:

Int * const r = & N;

// N has been declared as an int

Here, both p and q point to the const int type pointer, that is, you cannot change * P value in future programs. R is a const pointer, Which is initialized to point to the variable N (that is, r = & N;) when declared, the R value is no longer allowed to be changed (but * The R value can be changed ).

 

In combination with the above two const modifiers, we declare a const pointer pointing to the const int type, as follows:

Const int * const P = & n

// N has been declared as const int

 

The following statements about const will help you thoroughly clarify the usage of Const. However, note that the following statements cannot be compiled because they must be initialized at the same time. For the sake of conciseness, I ignored the initialization part. Because the initialization code is added, two lines of code will be added for each declaration below.

Char ** P1;

// Pointer to Char

Const char ** P2;

// Pointer to const char

Char * const * P3;

// Pointer to const pointer to Char

Const char * const * P4;

// Pointer to const char

Char ** const P5;

// Const pointer to Char

Const char ** const P6;

// Const pointer to const char

Char * const P7;

// Const pointer to Char

Const char * const P8;

// Const pointer to const char

 

Note: P1 is the pointer to the char type; P2 is the pointer to the const char type; P3 is the const pointer to the char type; p4 is the const pointer to the const char type; P5 is the const pointer to the char type; P6 is the const pointer to the const pointer of the const char type; p7 is the const pointer to the const pointer of the char type; P8 is the const pointer to the const pointer of the const char type.

 

The use of typedef

Typedef provides you with a way to overcome the disadvantages of "* Only applicable to variables but not types. You can use typedef as follows:

Typedef char * pchar;

Pchar p, q;

Both p and q are declared as pointers. (If typedef is not used, Q will be declared as a char variable, which is inconsistent with our first glance !) The following are some declarations using typedef and explanations:

Typedef char *;

// A is a pointer to a char

Typedef a B ();

// B is a function that returns

// A pointer to a char

Typedef B * C;

// C is a pointer to a function

// That returns a pointer to a char

Typedef c d ();

// D is a function returning

// A pointer to a function

// That returns a pointer to a char

Typedef D * E;

// E is a pointer to a function

// Returning a pointer to

// Function that returns

// Pointer to a char

E var [10];

// VaR is an array of 10 pointers

// Functions returning pointers

// Functions returning pointers to chars.

 

Typedef is often used before a schema declaration, as shown below. In this way, when creating a structure variable, you are allowed not to use the keyword struct (in C, the keyword struct is required when creating the structure variable, such as struct tagpoint; in C ++, struct can be ignored, for example, tagpoint B ).

Typedef struct tagpoint

{

Int X;

Int y;

} Point;

 

Point P;

 

Function pointer

Function pointers are probably the most confusing statements. Function pointers are used most frequently when writing TSR programs in the DOS era. in Win32 and X-Windows, they are used when callback functions are needed. Of course, function pointers are also needed in many other places: virtual function tables, some STL templates, Win NT/2 k/XP system services, etc. Let's take a simple example of a function pointer:

INT (* p) (char );

Here P is declared as a function pointer. This function carries a char type parameter and has an int type return value. In addition, a function pointer with two float type parameters and a pointer whose return value is char type can be declared as follows:

Char ** (* p) (float, float );

How should we declare a const pointer parameter with two Char Types and a function pointer without return values? Refer to the following:

Void * (* A [5]) (char * const, char * const );

The "right-left rule" is a simple rule, but it allows you to understand all statements accurately. This rule uses the following code: Read the statement from the inner brackets, read the statement to the right, and then look to the left. When you encounter a bracket, you can adjust the reading direction. All content in the brackets is analyzed and out of the brackets. This continues until the entire statement is analyzed.

 

Make a small correction to the "left-right rule": when you first read the declaration, you must start with the variable name instead of the innermost brackets.


The following example demonstrates the use of the "right left rule.

Int * (* FP1) (INT) [10];

Reading steps:

1. Start from the variable name -- FP1

2. Look to the right, there is nothing, and it is met), so when you look to the left, you will encounter a *-a pointer.

3. Jump out of brackets and encounter (INT) -- a function with an int Parameter

4. Look left and find a * -- (function) returns a pointer.

5. jump out of the brackets and look to the right. [10] -- an array of 10 elements

6. Look left and find a * -- pointer

7. Look left and find int-int type

Conclusion: FP1 is declared as a pointer to a function, which returns a pointer to a pointer array.

 

Let's look at an example:

Int * (* arr [5]) ();

Reading steps:

1. Start from the variable name -- arr

2. Looking to the right, it is found to be an array-an array of 5 elements

3. Look left and find a * -- pointer

4. Jump out of brackets and look to the right to find () -- A function without Parameters

5. To the left, a pointer is returned when * -- (function) is encountered.

6. Jump out of brackets and find () to the right-a function without Parameters

7. To the left, the * -- (function) returns a pointer.

8. Continue to the left. Int-int type is found.

 

There are more examples:

Float (* B () []) ();

// B is a function that returns

// Pointer to an array of pointers

// To functions returning floats.

Void * (* C) (char, INT (*)());

// C is a pointer to a function that takes

// Two parameters:

// A char and a pointer to

// Function that takes no

// Parameters and returns

// An int

// And returns a pointer to void.

Void ** (* D) (Int &,

Char ** (*) (char *, char **));

// D is a pointer to a function that takes

// Two parameters:

// A reference to an int and a pointer

// To a function that takes two parameters:

// A pointer to a char and a pointer

// To a pointer to a char

// And returns a pointer to a pointer

// To a char

// And returns a pointer to void

Float (* E [10])

(Int &) [5];

// E is an array of 10 pointers to // functions that take a single // reference to an int as an argument // and return pointers to // an array of 5 floats. supplement: all the complex pointer declarations in C language are made up of various declarations nested. How can we interpret complex pointer declarations? The left-right rule is a well-known and commonly used method. However, the right-left rule is not actually the content in the C standard. It is a method summarized from the C standard statement. The C-standard declaration rules are used to solve how to create a declaration, while the right-left rule is used to identify a declaration. The two are the opposite. The right-left rule: start reading the declaration from the innermost parentheses, go right, and then go left. when you encounter parentheses, the direction shoshould be reversed. once everything in the parentheses has been parsed, jump out of it. continue till the whole declaration has been parsed. the left-right principle: first, look up from the parentheses in the innermost part, then look up to the right, then look to the left. When parentheses are encountered, the reading direction should be dropped. Once everything in the parentheses is parsed, the parentheses appear. Repeat this process until the entire declaration is parsed. I want to make a small correction to this rule. It should be read from undefined identifiers rather than from parentheses. The reason is that there may be multiple identifiers in a declaration, but there is only one undefined identifier. Now we can use some examples to discuss the application of the right-left rule. First, we will gradually deepen the process from the simplest point: int (* func) (int * P); first, we will find the undefined identifier, it is func. It has a pair of parentheses on the outside, and there is a * sign on the left. This indicates that func is a pointer, and then jumps out of the parentheses. Let's look at the right and it is also a parentheses, this indicates that (* func) is a function, and func is a pointer to this type of function, which is a function pointer. This type of function has an int * type parameter and the return value type is int. INT (* func) (int * P, INT (* f) (int *); func is included in a pair of parentheses and there is a * sign on the left, which indicates that func is a pointer, if there is a bracket on the right side of the brackets, func is a pointer to the function. Such functions have parameters such as int * and INT (*) (int, the return value belongs to the int type. Let's take a look at the func parameter int (* f) (int *). Similar to the previous explanation, F is also a function pointer. the pointer to a function has an int * type parameter and the return value is int. INT (* func [5]) (int * P); The right side of func is a [] Operator, indicating that func is an array with five elements, and there is a * on the left of func *, it indicates that the element of func is a pointer. Note that * not modifying func, but modifying func [5] is because the [] operator has a higher priority, func is first combined with [], so * modifies func [5]. Jump out of this bracket and look at the right. It is also a pair of parentheses, indicating that the element of the func array is a pointer of the function type. It points to the function with an int * type parameter and the return value type is int. INT (* func) [5]) (int * P); func is included by a circle bracket, and there is a * on the left. Then func is a pointer and jumps out of the brackets, the right side is a [] operator number, indicating that func is a pointer to an array. Now, to the left, there is a * number, indicating that the element of this array is a pointer and then jumps out of brackets, there is a bracket on the right, indicating that the element of this array is a pointer to the function. To sum up, func is a pointer to an array. The elements of this array are function pointers. These pointers point to functions with int x parameters and return values of the int type. INT (* func) (int * p) [5]; func is a function pointer, which has an int * type parameter and returns a pointer to an array, the element of the array to which it points is an array with five int elements. Note that some complex pointer declarations are invalid. For example, int func (void) [5]; func is a function that returns an array with five int elements. However, the function return value in C language cannot be an array. This is because if the function return value is an array, what receives the content of this array must also be an array, however, the array name in C language is a right value, which cannot be used as the left value to receive another array. Therefore, the return value of the function cannot be an array. Int func [5] (void); func is an array with five elements. All elements of this array are functions. This is also invalid because the elements of the array must be of the same type and the memory space occupied by each element must be the same. Obviously, the function cannot meet this requirement, even if the function type is the same, the space occupied by the function is usually different. As an exercise, the following lists several complex pointer declarations for readers to resolve themselves. The answer is placed in chapter 10. INT (* func) [5] [6]) [7] [8]; int (* func) (int *) [5]) (int *); int (* func [7] [8] [9]) (int *) [5]; in reality, when you need to declare a complex pointer, if the entire statement is written as shown above, the readability of the program is greatly impaired. Typedef should be used to decompose declarations layer by layer to enhance readability. For example, for declaration: int (* func) (int * p) [5]; it can be decomposed as follows: typedef int (* para) [5]; typedef para (* func) (int *); this makes it much easier to see.

How to understand the complex types of C and C ++ statements (transfer)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.