1. 程式人生 > >《Beginning C++17》-學習筆記-Chapter 06-Pointers and References

《Beginning C++17》-學習筆記-Chapter 06-Pointers and References

#include <iostream>

int main()
{
	long* pnumber1{};//the statement initializes pnumber1 with the pointer equivalent of zero, which is a special address that doesn't point to anything.
	long* pnumber2{ nullptr };/*this statement is same as above, but makes it more explicit that  punmber2 points to a null pointer;
							  It's common to use variable names beginning with p for pointers.
							  */
	long *pnumber3{};/*the asterisk can be positioned adjacent to the variable name.*/

	long *pnumber4{}, number{};/*This defines pnumber4 of type "pointer to long", which is initialized with nullptr, and number of type
							   long, which is initialized with 0L*/
	long *pnumber5{}, *number2{};/*Define two variables of type "pointer to long*/
}

All pointers for a given platform will have the same size.

#include <iostream>
int main()
{
	// Print out the size (in number of bytes) of some data types
	// and the corresponding pointer types:
	std::cout << sizeof(double) << " > " << sizeof(char) << std::endl; //output 8 > 1;
	std::cout << sizeof(double*) << " == " << sizeof(char*) << std::endl;//output 4 == 4;
}

#include <iostream>
int main()
{
	long height{};
	auto* pheight{ &height }; /*variable pheight has deduced type: long* (pointer to long).
							  A variable declared with auto* can be initialized only with a pointer value.*/
	std::cout << *pheight << std::endl;//output 0; the indirection operator is often called the dereference operator as well.
	
	*pheight = 100;
	std::cout << height << std::endl;//output 100; using a dereferenced pointer is the same as using the variable to which it points.
}

#include <iostream>

int main()
{
	const char* pproverb{ "A miss is as good as a mile." }; /*This defines pproverb to be of type const char*.Because it is a pointer - to - const type, the type is consistent with that of the string literal.*/
	std::cout << pproverb << std::endl; /*Using the pointer name to output a string pointed to by a pointer.*/
	std::cout << *pproverb << std::endl; /*applying the insertion operator to a pointer to type char that is not dereferenced presumes that the pointer contains the address of a null-terminated string. If you output a dereferenced pointer to type char, the single character at that address will be written to cout. */
}

#include <iostream>
#include <array>             // for std::size()

int main()
{
	const char* parray[]{ "1st element", "2nd element", "3rd element"};// define an array of pointer;specifying that the char elements pointed to by the elements of the parray array are constant.
	//*parray[0] = 'X'; // This statement will not compile...
	parray[0] = parray[1];// this hasn’t changed the object pointed to by the first array element—it has only changed the address stored in it, so the const specification hasn’t been contravened.
	for (unsigned int n{}; n < std::size(parray); ++n)
	{
		std::cout << parray[n] << std::endl;
	}

	const char* const parray2[]{ "1st element", "2nd element", "3rd element" };//the pointers and the strings they point to are both defined as constant. Nothing about this array can be changed.
	//parray2[0] = parray[1]; // This statement will not compile...

	//For a pointer to a constant: You can’t modify what’s pointed to, but you can set the 	pointer to point to something else, as illustrated by the code snippets below.
	const char* my_favorite_word{ "Tranquility" };//This defines an array that contains const char elements.
	//my_favorite_word[1] = 'o'; // Error: my_favorite_word[1] is const and can not be changed.

	my_favorite_word = "Fighting";// the pointer value stored in my_favorite_word is overwritten with const char elements

	const int value{ 20 };
	const int* pvalue{ &value }; /* Here value is a constant and can’t be changed, and pvalue is a pointer to a
		constant, so you can use it to store the address of value.You couldn’t store the
		address of value in a pointer to non - const int(because that would imply that
			you can modify a constant through a pointer), but you could assign the address of
		a non - const variable to pvalue.In the latter case, you would be making it illegal
		to modify the variable through the pointer.In general, it’s always possible to
		strengthen const - ness, but weakening it isn’t permitted.*/

	/*for a constant pointer : The address stored in the pointer can’t be changed.A constant
		pointer can only ever point to the address that it’s initialized with.However, the
		contents of that address aren’t constant and can be changed.Suppose you define an
		integer variable data and a constant pointer pdata :*/

	int data{ 20 };
	int* const pdata{ &data };

	/*pdata is const, so it can only ever point to data.Any attempt to make it point
		to another variable will result in an error message from the compiler.The value
		stored in data isn’t const, though, so you can change it :*/

	*pdata = 25; // Allowed, as pdata points to a non-const int

	/*if data was declared as const, you could not initialize pdata with &data.
		pdata can only point to a non - const variable of type int.*/


	/* for a constant pointer to a constant : Here, both the address stored in the pointer and
		the item pointed to are constant, so neither can be changed.*/

	const float value1{ 3.1415f };

	const float* const pvalue1{ &value1 };
	
	/*pvalue1 is a constant pointer to a constant.You can’t change what it points to, and
		you can’t change what is stored at that address.*/

	//Read all type names right to left. Read every asterisk as “pointer to.” 
	const float f_value{};
	float const* const pvalue3{ &f_value };
	/*Reading right to left then reveals that pvalue3 is indeed a const pointer to const floats.
	The only additional complication is that the first const is typically written prior to the element type as shown in the following statement*/
	const float* const pvalue4{ &f_value };//pvalue4 is a const pointer to const floats.
	
}

#include <iostream>

int main()
{
	/*An array name can be interpreted as an address and be used to initialize a pointer*/
	double values[10];
	double* pvalue{ values }; /* This statement will store the address of the values array in the pointer pvalue.
							  Although an array name represents an address, it is not a pointer.
							  You can modify the address stored in a pointer, whereas the address that an array name represents is fixed.*/

	/*Pointer arithmetic implicitly assumes that the pointer points to an array.
	Incrementing a pointer by 1 means incrementing it by one element of the type to which it points. 
	Adding 1 to a pointer increments the pointer so that it points to the next element in the array. */

	/*The precedence of the indirection operator is higher than that of the arithmetic operators, + and -. 
	The expression *pvalue + 1 adds 1 to the value stored at the address contained in pvalue. 
	The result of *pvalue + 1 is a numerical value, not an address*/

	long numbers[]{ 10, 20, 30, 40, 50, 60, 70, 80 };//define an array
	long *pnum1{ &numbers[6] }; // Points to 7th array element
	long *pnum2{ &numbers[1] }; // Points to 2nd array element

	auto difference{ pnum1 - pnum2 }; 
	std::cout << difference <<std::endl;// Result is 5
	/*the difference between two pointers is again measured in terms of elements, 
	not in terms of bytes. */
	
	/*The C++ language prescribes that subtracting two pointers results in a value 
	of type std::ptrdiff_t, a platform - specific type alias for one of the signed 
	integer types defined by the cstddef header.
	std::ptrdiff_t is typically an alias for either int, long, or long long.*/
	std::ptrdiff_t difference2{ pnum2 - pnum1 }; // Result is -5
	std::cout << difference2 << std::endl;

	//comparing pointers
	std::cout << (pnum1 > pnum2) << std::endl; //result is 1.

}

#include <iostream>

int main()
{
	long data[5]{1,2,3,4,5};
	std::cout << data[3] << std::endl;
	std::cout << *(data + 3) << std::endl; //using pointer notation.
	/* The array name by itself refers to the address of the beginning of the array*/
}
//check if a number entered is prime.
#include <iostream>
#include <iomanip>

int main()
{
	while (1)
	{
		unsigned int trial{};
		std::cout << "Enter a Number you want to check if it is a prime: " << std::endl;
		std::cin >> trial;

		bool isprime{ true };               // Indicates when a prime is found

		while (true)
		{
			// Try dividing the candidate by 1 to itself
			for (size_t i{ 2 }; i < trial && isprime; ++i)
			{
				isprime = (trial % i > 0);
			}
			break;
		}

		std::cout << "The number you entered " << (isprime ? "is " : "is not ") << "a prime." << std::endl;
	}
}
/*When you allocate memory dynamically, the space that is made available is identified by its
address. The only place to store this address is in a pointer.*/

/* Variables for which memory is allocated at runtime always have dynamic storage duration.*/

/*An automatic variable is created when its definition is executed. The space for an automatic
variable is allocated in a memory area called the stack. 
At the end of the block in which an automatic variable is defined, the memory allocated for the
variable on the stack is released and is thus free to be reused.*/

/*Memory that is not occupied by the operating system or other programs that are currently loaded
is called the free store. You can request that space be allocated within the free store at runtime for a new
variable of any type. You do this using the new operator, which returns the address of the space allocated, and
you store the address in a pointer. The new operator is complemented by the delete operator, which releases
memory that you previously allocated with new.
 If you don’t use delete to release the memory, it will be released automatically when program execution ends.*/

#include <iostream>

int main()
{
	double* pvalue{}; // Pointer initialized with nullptr
	pvalue = new double; 
	/*new operator in the second line of the code returns the address of the memory in the free store
allocated to a double variable, and this is stored in pvalue.*/
	*pvalue = 5.5;
	std::cout << *pvalue << std::endl; //output 5.5

	double* pvalue2{};
	pvalue2 = new double{ 3.14 }; // Allocate a double and initialize it
	std::cout << *pvalue2 << std::endl; //output 3.14

	double* pvalue3{ new double {6.6} }; // Pointer initialized with address in the free store
	std::cout << *pvalue3 << std::endl; //output 6.6

	delete pvalue; // Release memory pointed to by pvalue
	/* After the previous statement has executed, pvalue still contains the address of the memory that 
	was allocated, but the memory is now free and may be allocated immediately to something else. 
	A pointer that contains such a spurious address is sometimes called a dangling pointer. */
	/* you should get in the habit of always resetting a pointer when you release the memory 
	to which it points, like this:*/
	pvalue = nullptr; // Reset the pointer



}
// Calculating primes using dynamic memory allocation
/*all statements and expressions involving the primes array in this program use the array
notation. But only because this is easier; you could equally well use and write them using pointer notation:
*primes = 2, *(primes + i), *(primes + count++) = trial, and so on.*/

#include <iostream>
#include <iomanip>
#include <cmath>                                             // For square root function

int main()
{
	size_t max{};                                             // Number of primes required

	std::cout << "How many primes would you like? ";
	std::cin >> max;                                           // Read number required

	if (max == 0) return 0;                                    // Zero primes: do nothing

	auto* primes{ new unsigned[max] };                          // Allocate memory for max primes
	/*The address that’s returned by new is stored in the pointer, primes. This will be the address of the
first element of an array of max elements of type unsigned (int).*/

	size_t count{ 1 };                                          // Count of primes found
	primes[0] = 2;                                             // Insert first seed prime

	unsigned trial{ 3 };                                        // Initial candidate prime

	while (count < max)
	{
		bool isprime{ true };                                     // Indicates when a prime is found

		const auto limit = static_cast<unsigned>(std::sqrt(trial));
		for (size_t i{}; primes[i] <= limit && isprime; ++i)
		{
			isprime = trial % primes[i] > 0;                       // False for exact division
		}

		if (isprime)                                             // We got one...
			primes[count++] = trial;                               // ...so save it in primes array

		trial += 2;                                              // Next value for checking
	}

	// Output primes 5 elements in a line 
	for (size_t i{}; i < max; ++i)
	{
		std::cout << std::setw(10) << primes[i];
		if ((i + 1) % 5 == 0)                                   // After every 5th prime...
			std::cout << std::endl;                                // ...start a new line
	}
	std::cout << std::endl;

	delete[] primes;                                           // Free up memory...
	primes = nullptr;                                          // ... and reset the pointer
	/*it’s good to get into the habit of always resetting a pointer after freeing the memory to which it points*/

	/* if a vector<> container is used to store the primes, it can be deleted when the computation is done; it’s all taken care of
by the container. In practice, one should therefore nearly always use std::vector<> to manage dynamic memory.*/

}
#include <iostream>
#include <vector>
int main()
{

	auto* pdata{ new std::vector<int>{} };
	/*pdata is a pointer to a vector<> container which is allocated in the free store with this statement.
	The compiler deduces the type of pdata to be vector<int>*, which is a “pointer to a vector of int elements.”
	To access the vector object using the pointer, you could use the dereference operator*/
	(*pdata).push_back(66); // Add an element containing 66

	/*C++ provides an operator that combines dereferencing a pointer to an object and then selecting
	a member of the object.*/
	pdata->push_back(77); // Add another element containing 77

	/*-> operator is formed by a minus sign and a greater - than character and is referred to as the arrow
	operator or indirect member selection operator.*/

	std::cout << pdata->at(0) << std::endl;//output 66
	std::cout << pdata->at(1) << std::endl;//output 77
	   
}
int main()
{

	/* always reset a pointer to nullptr after the memory it points to is released. */

	/*Every new must be paired with a single delete; every new[] must be paired with 
	a single delete[]. */

	/* If you lose the address of free store memory you have allocated, by overwriting
	the address in the pointer you were using to access it, for instance, you have a
	memory leak.
	The lifetime of a pointer extends from the point at which you define it in a block
	to the closing brace of the block. After that it no longer exists, so the address it 
	contained is no longer accessible. If a pointer containing the address of a block of 
	memory in the free store goes out of scope, then it’s no longer possible to delete 
	the memory.
	One basic strategy for avoiding memory leaks is to immediately add the delete operation
	at an appropriate place each time you use the new operator.*/

	/*Never use the operators new, new[], delete, and delete[] directly in day-to-day coding.
These operators have no place in modern C++ code. Always use either the std::vector<> container
(to replace dynamic arrays) or a smart pointer (to dynamically allocate objects and manage their lifetimes).
These high-level alternatives are much, much safer than the low-level memory management primitives and
will help you tremendously by instantly eradicating all dangling pointers, multiple deallocations, allocation/
deallocation mismatches, and memory leaks from your programs.*/
  
}

/*Smart pointers are normally used only to store the address of memory allocated in the free store.
Using a smart pointer, you don’t have to worry about using the delete or delete[] operator to free the memory.
It will be released automatically when it is no longer needed.
smart pointers can be stored in an array<T,N> or vector<T> container.
Smart pointer types are defined by templates inside the memory header of the Standard Library.*/

/*A unique_ptr<T> object behaves as a pointer to type T and is “unique” in the sense
that there can be only one single unique_ptr<> object containing the same address.
In other words, there can never be two or more unique_ptr<T> objects pointing to
the same memory address at the same time. A unique_ptr<> object is said to own
what it points to exclusively. This uniqueness is enforced by the fact that the compiler
will never allow you to copy a unique_ptr<>.
While copying a unique_ptr<> is not possible, you can “move” the address stored by
one unique_ptr<> object to another by using the std::move() function. After this move operation the original smart
pointer will be empty again.
*/

/*A shared_ptr<T> object also behaves as a pointer to type T, but in contrast with
unique_ptr<T> there can be any number of shared_ptr<T> objects that contain—or,
share—the same address. At any given moment, the number of shared_ptr<> objects
that contain a given address in time is known by the runtime. This is called reference
counting. The reference count for a shared_ptr<> containing a given free store
address is incremented each time a new shared_ptr<> object is created containing
that address, and it’s decremented when a shared_ptr<> containing the address
is destroyed or assigned to point to a different address. When there are no shared_
ptr<> objects containing a given address, the reference count will have dropped to
zero, and the memory for the object at that address will be released automatically. All
shared_ptr<> objects that point to the same address have access to the count of how
many there are. */

/*A weak_ptr<T> is linked to a shared_ptr<T> and contains the same address.
Creating a weak_ptr<> does not increment the reference count associated to the
linked shared_ptr<> object, though, so a weak_ptr<> does not prevent the object
pointed to from being destroyed. Its memory will still be released when the last
shared_ptr<> referencing it is destroyed or reassigned to point to a different address,
even when associated weak_ptr<> objects still exist. If this happens, the weak_ptr<>
will nevertheless not contain a dangling pointer, at least not one that you could
inadvertently access. The reason is that you cannot access the address encapsulated
by a weak_ptr<T> directly. Instead, the compiler will force you to first create a
shared_ptr<T> out of it that refers to the same address. If the memory address for
the weak_ptr<> is still valid, forcing to create a shared_ptr<> first makes sure that
the reference count is again incremented and that the pointer can be used safely
again. If the memory is released already, however, this operation will result in a
shared_ptr<> containing nullptr.*/

/*One use for having weak_ptr<> objects is to avoid so-called reference cycles with shared_ptr<> objects.
Conceptually, a reference cycle is where a shared_ptr<> inside an object x points to some other object y
that contains a shared_ptr<>, which points back to x. With this situation, neither x nor y can be destroyed.
In practice, this may occur in ways that are more complicated. weak_ptr<> smart pointers allow you to break
such reference cycles. Another use for weak pointers is the implementation of object caches.*/

#include <memory>
#include <iostream>
#include <iomanip>
int main()
{

	std::unique_ptr<double> pdata{ new double{999.0} };
	/*The above is the old way of creating and initializing a unique_ptr<T> object*/

	std::unique_ptr<double> pdata2{ std::make_unique<double>(993.0) };
  /* The above is the recommended way to create a unique_ptr<> today, which is by means of
the std::make_unique<>() function template (introduced by C++14).*/

	/*The above statement can be further simplified as follows.*/
	auto pdata3{ std::make_unique<double>(995.0) };/*This is the best way of creating a std::unique_ptr<T> object that points to a newly allocated T value*/

	std::cout << *pdata3 << std::endl; // Outputs 995

	std::cout << std::hex << std::showbase << pdata3.get() << std::endl;
	/*This outputs the value of the address contained in pdata3 as a hexadecimal value. */

	const size_t n{ 100 }; // Array size
	auto pvalues{ std::make_unique<double[]>(n) }; // Dynamically create array of n elements

	for (size_t i{}; i < n; ++i)
		pvalues[i] = i + 1;
	/*This sets the array elements to values from 1 to n.*/

	for (size_t i{}; i < n; ++i)
	{
		std::cout << std::setw(3) << pvalues[i] << ' ';
		if ((i + 1) % 10 == 0)
			std::cout << std::endl;
	}
	/*This just outputs the elements of the array ten on each line. */

	pvalues.reset(); // pvalues is reset to point to nullptr and  the memory for the array is released as a result.

	std::cout << pvalues << std::endl;//output 00000000

	//Three ways of creating a smart pointer that contains nullptr 
	std::unique_ptr<int> my_number1; 
	std::unique_ptr<int> my_number2{};
	std::unique_ptr<int> my_number3{ nullptr };

	std::cout << my_number1  << " " << my_number2 << " " << my_number3 << std::endl;

	my_number1.reset(new int{ 123 }); // my_number1 is reset to point to an integer value 123 now.
	std::cout << my_number1 << std::endl;//output an address
	std::cout << *my_number1 << std::endl;//output 0x7b
	std::cout << std::noshowbase << *my_number1 << std::endl;//output 7b
	std::cout << std::dec << *my_number1 << std::endl;//output 123

	my_number1.reset();// my_number1 is reset to point to nullptr.
	std::cout << my_number1 << std::endl;

	my_number2.reset(new int{ 12 });
	std::cout << *my_number2 << std::endl;//output 12
	my_number2.reset(nullptr);// my_number2 is reset to point to nullptr.
	std::cout << my_number2 << std::endl;//output 00000000


}