c++ pointers help

Started by
30 comments, last by nbertoa 16 years, 1 month ago
Quote:Original post by gamedev99
watch the video here. http://cslibrary.stanford.edu/104/

In short, they point to things.

Can't believe I actually watched that video.
Anyways if I wasn't already familiar with pointers that video would've just confused me more. I like the part where mr potato head blew up though--didn't see that one coming-LOL!


[size="2"]Don't talk about writing games, don't write design docs, don't spend your time on web boards. Sit in your house write 20 games when you complete them you will either want to do it the rest of your life or not * Andre Lamothe
Advertisement
Quote:Original post by gamedev99
watch the video here. http://cslibrary.stanford.edu/104/

In short, they point to things.



Couldn't stop myself from laughing after watching this video

Gents nandu Intelligents veyrayaa

Values



C++ programs manipulate values. Typical values include integers, such as 42, characters, such as 'x', objects of more complex types, such as std::string or std::fstream, and also instances of any class you define in your program.

Variables and References



Values need to be manipulated, which involves giving them a name. A reference is a name given to a value. Creating a reference is done like so:
Type & name = value;
Where value is usually the return value of a function or operator:
std::ostream &o = (std::cout << "Text.");
but might also be another name given to the same value:
std::ostream &o = std::cout;


Another way to create a reference is as a function argument:
void foo(Type & name)
This will cause the value which was passed as argument to be available under that name in the entire function body. This can be quite useful if you wish to modify that value.

Of course, almost any program needs to create values. In C++, values are always created using operator new (except for literals such as 42 or "Hello", which simply exist). operator new exists in several versions. The default version generates dynamic values: these values exist until delete is called:
int & value = * new int;value = 10;std::cout << value;delete (&value);
This causes performance problems if used too often, because dynamic memory allocation is slow in a non-garbage-collected language like C++. This is why another, faster version exists: placement new creates a new value using existing memory. Of course, the lifetime of the value is then limited by the lifetime of the memory which was used to create it, and it must of course be manually destroyed by calling its destructor:
std::string & str = * new (existing_memory) std::string("Hello");str += " world!";std::cout << str;str.~string();// existing_memory dies somewhere after this line
The existence of placement new allows the programmer to use memory areas which are faster than the free store used by the default operator new: it may use stack memory, a specially reserved portion of global memory, or a subset of the memory used by another value, by increasing speed of allocation. For instance, using stack memory:
int & value = * new (alloca(sizeof(int))) int;


Since allocating objects on the stack, in global memory or as a subset of an object is a very repetitive task which involves obtaining the memory, computing the size to be allocated, and then calling the destructor of the object right before the memory is gone, the C++ language provides shortcuts to do this. Take for example stack allocation of a string:
void sayhello() {  std::string & str = * new (alloca(sizeof(std::string))) std::string("Hello");  str += " world!";  std::cout << str;  str.~string();}
I have underlined the repetitive tasks that are related to stack allocation. The shortcut provided by C++ in this situation is as follows:
void sayhello() {  auto std::string str("Hello");  str += " world!";  std::cout << str;}
The main differences here are the presence of the auto keyword, the absence of the & in the definition of the reference, and the absence of the str.~string() destructor call at the end of the function. The first two differences cause C++ to allocate a new block of memory on the stack (auto) which will be released when the function returns and initialize it with a new value from the string literal "Hello". The auto keyword also causes the compiler to generate destructor calls: the object will be automatically destroyed right before the function exits (since that is when the stack allocation is released). In this situation auto is aptly named a storage specifier.

The other storage specifier is static, which uses global memory: defining a local variable as static causes the compiler to allocate some memory for it as part of the global data span (at compile-time) and then initialize the value at that spot in memory the first time the variable's definition is encountered. Since auto is the default storage specifier, it is generally omitted from the definition, which leads to the typical variable definition that most C and C++ programmers are used to:
std::string str("Hello");str += " world!";std::cout << str;


Variables at global scope automatically use global storage. Values in global storage are destroyed in the reverse order of their creation when the program exits. Global variables (as opposed to local static ones) are initialized in unspecified order, and may remain uninitialized if nobody uses them.

Member variables of structures or classes use the third type of allocation: they use a bit of reserved memory that was part of the value of which they are a member, and they are initialized there as part of that value's constructor. Their destructor is automatically called as part of the value's destructor, in their reverse order of initialization. Before:
struct super{  std::string & str;  some_memory_buffer;  super() : str(* new (some_memory_buffer) std::string) {}  ~super() { str.~string(); }};
After:
struct super {   std::string str;   // No explicit memory buffer  // Automatic construction  // Automatic destruction};


To summarize: references are names given to values. C++ provides special shortcuts to create a value and bind it to a reference at the same time, with special rules about when the created value is to be destroyed.

Iterators and Sequences



A sequence is, as its name hints at, a sequence of values. It could be, for instance, the sequence 1, 2, 3, 4. Or, it could be the set of opponents in a video game, in an arbitrary order. Or, it could be the sequence of characters being read from std::cin. In short, sequences are the fundamental concept used to represent groups of objects that are to be manipulated together.

A typical representation of sequences is through iterators, which are means of iterating over a sequence (accessing its elements in order). The basic operations required to iterate are the ability to read the "current value", the ability to move on to the "next value", and the ability to determine if the end of the sequence was reached and there are no values left. For instance, pseudocode to add together values of a sequence through iteration:
a = 0while not end-of-sequence(iter)  a += value(iter)  iter = next(iter)


Various languages provide various forms of iterators, but the three operations outlined above are always present. For instance, Objective Caml uses (it = []), List.hd it and List.tl it as the end-of-sequence, value and next operations. C++ uses it == end, *it and ++it as the end-of-sequence, value and next operations. So, C++ code to add together the values of a sequence (represented by two iterators of type Iter representing the first iterator of the sequence and the first iterator past the sequence) would be:
template<typename Iter>int sum(Iter begin, Iter end){  int a = 0;  while (begin != end)   {    a += *begin;    ++begin;  }  return a;}


The begin-end representation of sequences is standard. The end iterator is the first iterator after the sequence: as such, it has no associated value, and trying to obtain the iterator after end is invalid. So, you cannot *end or ++end. You can, however, compare end with another iterator to see if that other iterator has reached the end of the sequence.

The point of using a past-the-end iterator (one that isn't in the sequence, but is actually right after it) instead of a last-element iterator (one that corresponds to the last element in the sequence) is twofold. First, it makes code more complex, because the condition "I have reached the past-the-end iterator on this iteration" is easier to evaluate than "I was working on the last iterator on the previous iteration". Second, it allows representation of empty sequences (since the empty sequence has no last element and as such could not be represented using a last-iterator approach).

An iterator which supports only *it and ++it is called a forward iterator. Some iterators also support --it (which moves to the previous element), making it a bidirectional iterator. Some iterators also support it + n and it - n, allowing the iterator to move an arbitrary number of steps backward or forward in a single jump, which is called a random access iterator. Iterators may in turn be read-only (input iterator), write-only (output iterator) or support both read and write.

Iterators are a very powerful concept: most of the things in C++ which look like sequences can be turned into iterators.

  • The sequence of elements in a vector is represented by vect.begin() and vect.end(), which are random access read-write iterators (or read-only, if the vector is constant).

  • The sequence of elements in a list is represented by list.begin() and list.end(), which are bidrectional read-write iterators (or read-only, if the list is constant).

  • The sequence of objects of type T read from an std::istream such as std::cin is represented by std::istream_iterator<T>(std::cin) and std::istream_iterator<T>() (past-the-end iterator) which are forward input iterators.

  • Adding a sequence of elements at the end of any non-associative standard library container c is represented by std::back_inserter(c), with no end iterator (this could be an infinite sequence) and is a forward output iterator.


The list goes on and on. This system is then combined with iterator-based algorithms, such as std::copy(src_begin, src_end, dest_begin), where src_begin and src_end are at least forward input iterators representing the input sequence to be copied, and dest_begin is at least a forward output iterator representing where the input sequence should be copied to. As such, reading as many integers as possible from standard input and placing them in a vector is as simple as:
std::vector<int> integers;std::copy( std::istream_iterator<int>(std::cin),           std::istream_iterator<int>(),           std::back_inserter(integers) );


Or, we could add together the values read on the standard input using our function above:
std::cout << sum( std::istream_iterator<int>(std::cin),                  std::istream_iterator<int>() );
Just like we could add together the elements of a vector:
std::cout << sum( integers.begin(), integers.end() );


Pointers



Pointers are a concept originally introduced in C. From a semantic point of view, pointers are random access read-write iterators. As iterators, they represent sequences of contiguous elements of the same type—such sequences are created either using a vector, or using a block allocation approach such as new[] or arrays.

Given a block N objects of type T, a pointer representing that sequence is of type T*. Depending on the nature of the block, obtaining the first pointer begin in the sequence varies.
  • T *begin = new T[N]; will directly return the first pointer in the sequence.
  • If a is an array of N objects of type T then T *begin = a; will create the first pointer in the sequence of elements of a.

Otherwise, if t (of type T) is the first element of a sequence (as opposed to the first iterator of a sequence), then T *begin = &t is the first iterator of the sequence.

The end iterator of a sequence is simply defined as T *end = begin + N;
int values[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };std::cout << sum(values, values + 10);


As with any other iterator, going beyond the bounds of a sequence (before begin or after end) results in undefined behavior, which will usually lead to random misbehavior of your program.

All of the above also holds when N = 1 (which means that a single object is also a one-object sequence, which can as such be manipulated using a pointer).

The main problem with pointers is that this is not everything. If pointers were merely iterators, then people would have no more trouble with them than they would have with the other iterators in the standard library. However, pointers were introduced in the C language as a means to represent several other concepts.

The first of these concepts is reference semantics. You see, in C, there are on references per se: every time you create a variable, it also creates a value. This makes it difficult to handle dynamic memory (because every variable you create already has its own value, so how do you give your dynamic memory a name in order to use it?) and allow functions to modify a variable in another function (because all the arguments to your function are, by default, brand new values that will disappear when the function returns, and you have no access to the values in the calling function). The solution adopted was to decide that, since pointers are read-write iterators, and every value is a one-element sequence by definition, then pointers can be used to give indirect names to values.

For instance:
/* Solve aX² + bX + c */int find_solutions(float a, float b, float c, float *x1, float *x2){  float delta = b * b - 4f * a * c;  float sq;  if (delta < 0f) return 0;   sq = sqrt(delta);  *x1 = (- b - sq) / (2 * a);  *x2 = (- b + sq) / (2 * a);  return 2;}


This function needs to return the number of solutions (zero or two), but it also needs to send back the values of those solutions. In order to do so, it uses a trick which consists in being given two iterators to one-value sequences and modifying the unique value in each sequence. Since a copy of an iterator of a sequence is a new iterator of that same sequence, the iterator allows the function to modify the original sequence—and the values inside that sequence are the values from within the calling function.
int main(){  float x1, x2;  float *px1 = &x1 // An iterator to the one-element sequence 'x1'  float *px2 = &x2 // An iterator to the one-element sequence 'x2'  find_solutions(1f, 0f, -1f, px1, px2);  // The function call changed the contents of the sequences   // iterated by px1 and px2, namely x1 and x2. As such, the  // values of x1 and x2 have been changed.}


Of course, C++ provides references, which is why pointers are not used for pass-by-reference in C++ at all (as references provide a far more simple representation of one-element sequences).

Another use of pointers, which remained in C++, is the representation of reseatable references. In C++, a reference is a name given to a value, and the value is bound to the name forever. It is not possible to bind the name to another value (though it is possible, of course, to bind several names to the same value). Reseating a reference means binding it to another value. While impossible with references, pointers are merely iterators, and it's possible to assign an iterator to another (thereby changing the sequence that the iterator is traversing, or the point in the sequence where the iterator is)—after all, this is the entire point of traversing a sequence with a single iterator!

Therefore, when it becomes useful to have a reference to a value, but the ability to change that value is also useful, people sometimes use a pointer (though, in modern C++, there are other types, such as shared_ptr or weak_ptr, which do a far better job). For instance, if you wish to reference an enemy's target (and enemies can change targets at will):
struct enemy{  object *target;}


As such, an enemy's target can be changed by changing the pointer so that it corresponds to another value instead of the original one.

A third use of pointers, introduced in C, was the point of an option type. An option type allows the possibility for a value to be absent. For instance, one could decide that sqrt returns a float option: either its argument is positive and it returns a float, or it is negative and then it returns nothing.

Instead of defining an option type distinct from an iterator type (as C++ does for all its iterators), C merged the two together, and this carried over to C++. A pointer can be constructed from the integer constant zero, and thus becomes the null pointer. The null pointer may not be used in any way, except to be compared for equality with another pointer, or to be tested as a boolean condition (a null pointer always evaluates to false, while other pointers evaluate to true).

To summarize: pointers are bidirectional read-write iterators used in C for reference semantics and reseatable reference semantics, as well as a clunky option type.

Don't worry, this is just C++'s way of saying 'hello' [smile]
I found that quite useful even though it wasn't for me. Thank you ToohrVyk :)

To the OP, its usually a good idea to post some code with your posts to show that you are trying.

For example when I post, I put a little disclaimer at the top warning people that what I am about to post is from a finished class assignment that I have a few extra questions on. (Posting homework is not allowed but since it is finished and turned in already its usually ok, but I add a disclaimer anyway) then I post my code that is commented on the section I am confused about. I then reiterate at the bottom what section I am confused about and what I think should happen in the code. I have no problems getting help when I post like that.

Also small attention span is no excuse. You have to learn to work around it. My job does not allow the use of "mind altering medication" so its very hard for me (who has always had ADD) to study cause my mind is constantly switching to other things, but what I do is I study in chunks. I spend a few minuets working on programming, then a few min doing something else, then back to programming for a little bit longer this time, then to something else. It's possible to train your brain to think more normal, it just takes time. Give it a shot.
Quote:Original post by ToohrVyk
*** crazily long post removed ***


How long did that take to write? It ought to go in the forum FAQ.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Quote:Original post by rpstaekwondo
ok, i have asked you guys about functions, and arrays, and now can you guys plz explain pointers to me, im srry, but i really cant learn from a book, i dont have a large attention span, and i need to ask u guys about it, plz help, what is a pointer, and what do they do, and how do they work?


p.s. in a month i will ask about classes


Ok mate, so what do you want to know about pointers. I am reading C++ again [grin] It is better if you learn with your friend or someone together. It enables us to discuss and clear doubts etc and it can be more interactive [wink]

pointers are variables that hold memory location.

Ok I read it before but seem to forget. After looking at your thread I went through C++ book and wrote this simple program to differentiate between pointers and normal variables

Variables without pointers:

#include<iostream>using namespace std;int main(){    int a=5;    int b;    b=a;        a=a+1;    cout<<"\n the value of a is:"<<a;    cout<<"\n the address of a is:"<<&a;    cout<<"\n the value of b is:"<<b;    cout<<"\n the address of b is:"<<&b;    char response;    cin>>response;    return 0;          } output:the value of a is:6the address of a is:0x22ff74the address of b is:0x22ff70the value of b is:5   




Variables using pointers

 #include<iostream>using namespace std;int main(){    int a=5;    int *b;    b=&a;        a=a+1;    cout<<"\n the value of a is:"<<a;    cout<<"\n the address of a is:"<<&a;    cout<<"\n the value of b is:"<<b;    cout<<"\n the address of b is:"<<*b;    char response;    cin>>response;    return 0;          } output:the value of a is:6the address of a is:0x22ff74the address of b is:0x22ff74the value of b is:6   


As you can see pointers point to same address location and give same value of another variable and without pointers the address is different and the incremented value of a is printed but b is not.
Quote:Original post by oler1s
Quote:but i really cant learn from a book, i dont have a large attention span,
Sorry to hear that. Looks like your journey into the world of programming has to be cut short.


Mushu has an 1800+ rating and has been around here since 2003. He is obviously being facetious and/or mocking the OP.

To the OP: Just read ToohrVyk's post. Yes, it will be difficult; sugar-coating things doesn't help. But he's got the right information, in the right order (I'll admit I only skimmed it, but that's because I already know this stuff, and trust ToohrVyk from previous experience).

@kimi: the text labels on your output statements is somewhat misleading, and the apparently faked output doesn't match. Also, this only covers a small amount of the relevant material.
Quote:Zahlman:
Mushu has an 1800+ rating and has been around here since 2003. He is obviously being facetious and/or mocking the OP.
I quoted the OP.
Quote:Original post by swiftcoder
Quote:Original post by ToohrVyk
*** crazily long post removed ***


How long did that take to write? It ought to go in the forum FAQ.


Around 45 minutes. I had to cut short the explanation on null pointers because I went for lunch.

Quote:Original post by ToohrVyk
Quote:Original post by swiftcoder
Quote:Original post by ToohrVyk
*** crazily long post removed ***


How long did that take to write? It ought to go in the forum FAQ.


Around 45 minutes. I had to cut short the explanation on null pointers because I went for lunch.


First, I need to say thank you, you told me what i needed to know, and then some. I mean, 45 minuets, that is amazing!
I definitely think i know what i need to on the subject, thanks.

This topic is closed to new replies.

Advertisement