But how does a computer really do a function call? The expression(), term(), and primary() functions from Chapters 6 and 7 are perfect for illustrating this except for one detail: they don’t take any arguments, so we can’t use them to explain how arguments are passed. But wait! They must take some input; if they didn’t, they couldn’t do anything useful. They do take an implicit argument: they use a Token_stream called ts to get their input; ts is a global variable. That’s a bit sneaky. We can improve these functions by letting them take a Token_stream& argument. Here they are with a Token_stream& parameter added and everything that doesn’t concern function call implementation removed. First, expression() is completely straightforward; it has one argument (ts) and two local variables (left and t):
double expression(Token_stream& ts)
{
double left = term(ts);
Token t = ts.get();
// . . .
}
Second, term() is much like expression(), except that it has an additional local variable (d) that it uses to hold the result of a divisor for '/':
double term(Token_stream& ts)
{
double left = primary(ts);
Token t = ts.get();
// . . .
case '/':
{
double d = primary(ts);
// . . .
}
// . . .
}
I just wanna know what the point of doing this exactly was? Before making the functions take a reference argument ts was just a global object and the functions looked like this:
double term()
{
double left = primary();
Token t = ts.get();
// . . .
case '/':
{
double d = primary(ts);
// . . .
}
// . . .
}
Like just what is the point of doing this? We didn't need arguments why did the writer decide to add reference arguments to each function?
CodePudding user response:
[...] I know why global variables aren't the best but here if I do the thing with passing references as arguments am I not gonna need a global variable to pass anyways?
No.
You can pass a global variable, but you don't have to:
int main() {
Token_stream ts; // <- not a global
term(ts);
}
or
void foo() {
Token_stream ts; // <- not a global either
term(ts);
}
int main() {
foo();
}
If you consider only double term(Token_stream& ts)
in isolation then it doesn't matter if a global or not is passed as parameter. One main reason to write functions is that functions can be read/understood/tested/debugged in isolation. All you need is some paramters you can pass. With double term()
none of this is the case, because it is accessing global state. It is just spaghettis in disguise.
As mentioned in comments, the book you are using is taking an odd route. Instead of using globals as the default and then trying to argue why pass parameters instead, one should rather pass arguments to functions and only when there are good reasons use globals (there are almost never good reasons).
CodePudding user response:
Like just what is the point of doing this?
I want to answer this part.
Imagine you have
Token_stream ts;
int main()
{
term(); //accesses global ts
term(); //accesses global ts
}
and you are happy with this program. A few weeks later you want two token streams:
Token_stream ts1;
Token_stream ts2;
int main()
{
// first feature
term(); // you want this to accesses global ts1
term(); // you want this to accesses global ts1
// second feature
term(); // you want this to accesses global ts2
term(); // you want this to accesses global ts2
}
How are you going to implement this ? Suddenly, you need two globals and in order to change the usage of your code, you need to go in and modify term
itself.
This makes your code hard to use, hard to maintain and especially hard to test. Consider the alternative:
int main()
{
Token_stream ts1;
Token_stream ts2;
term(ts1);
term(ts1);
term(ts2);
term(ts2);
}
And, most importantly, no changes were needed to term
, to have a second Token_stream
.