CS 192 Lecture 15 Winter 2003 January 16, 2004 Dr. Shafay Shamail
Passing Values Ways to pass an argument to a function –By Value –By Address –By Reference ___________________________________ By Value #include int sqr_it(int x); int main() { int t=10; cout << sqr_it(t) << ' ' << t; //output? } int sqr_it(int x) { x = x*x; return x; } A copy of the value of argument t is passed to the formal parameter x. t remains unaltered. (copy) t 10 x
Passing Pointers By Address –We can use pointers to pass the address of the actual variable #include void sqr_it (int *x); int main() { int t = 10; sqr_it(&t); cout << t; return 0; } void sqr_it (int *x) { *x = (*x)*(*x); // var pointed to by x is assigned 100 } Original variable whose address is passed, i, is modified i 10 j 5FF 100
Passing References By Reference –Eliminates the need to manipulate pointers or to remember to pass address of argument. Not there in C #include void sqr_it(int &x); int main() { int t = 10; cout << "Old value for t: " << t << '\n'; sqr_it(t); // pass address of t to sqr_it() cout << "New value for t: " << t << '\n'; return 0; } void sqr_it(int &x) { x *= x; // this modifies calling argument t } Output: 110
References A reference is an alias for the variable. Simply follow the parameter’s type by an ampersand in prototype e.g. int &x, pronounced “x is a reference to an int” We passed the address of t to sqr_it() by assigning another name to t, which was x. Now the compiler automatically knows that whenever x is encountered, we mean t, because they are the same memory location To pass by reference, simply call the function with the variable name i.e. sqr_it(t) Now mentioning the variable by its parameter name (new alias) in the function body accesses the original variable in the calling function t, x 5FF one variable, two names
Independent References In addition to passing arguments by reference, can declare a stand-alone reference variable, called an independent reference #include int main() { int j, k; int &i = j; // independent reference j = 10; cout << j << " " << i; // outputs k = 121; i = k; // copies k's value into j cout << "\n" << j; // outputs 121 return 0; } An independent reference must be initialized when declared. How were we initializing reference parameters?
Independent References Output of following programme? #include int main() { int x = 3, &y; cout << "x = " << x << endl << "y = " << y << endl; y = 7; cout << "x = " << x << endl << "y = " << y << endl; return 0; } error C2530: 'y' : references must be initialized Output if we do int &y = x; ? Not a good idea to use independent references as not necessary and inherently confusing to have two names for same variable
References Output? #include int main() { int rats = 101; int &rodents = rats; cout << "rats = " << rats; cout << ", rodents = " << rodents << endl; rodents++; cout << "rats = " << rats; cout << ", rodents = " << rodents << endl; cout << "rats' address = " << &rats; cout << ", rodents' address = " << &rodents << endl; return 0; } Output: rats = 101, rodents = 101 rats = 102, rodents = 102 rats’ address = 006FD, rodents’ address = 0068FD
References #include int main() { int rats = 101; int &rodents = rats; cout << "rats = " << rats; cout << ", rodents = " << rodents << endl; cout << "rats' address = " << &rats; cout << ", rodents' address = " << &rodents << endl; int bunnies = 50; rodents = bunnies; //can we change the reference? cout << "bunnies = " << bunnies; cout << ", rats = " << rats; cout << ", rodents = " << rodents << endl; cout << "bunnies address = " << &bunnies; cout << ", rodents' address = " << &rodents << endl; return 0; } Output: rats = 101, rodents = 101 rats’ address = 5FF, rodents’ address = 5FF bunnies = 50, rats = 50, rodents = 50 bunnies address = 9AF4, rodents’ address = 5FF
References A reference keeps referring to the same variable to which it was initialized. You cannot change it by assignment later on int rats = 101; int *pi = &rats; int &rodents = *pi; int bunnies = 50; pi = &bunnies; cout << *pi << endl << rodents << endl << rats << endl; Output: Initializing rodents to *pi makes it refer to rats. Subsequently altering pi to point to bunnies does not alter the fact that rodents refers to rats
Returning References from Functions The return type of function determines that a reference is passed Can use such function on LHS of assignments! Can’t do so with ordinary functions that have the usual return types #include double &f(); double val = 100.0; int main() { double newval; cout << f() << '\n'; // display val's value newval = f(); // assign value of val to newval cout << newval << '\n'; // display newval's value f() = 99.1; // change val's value cout << f() << '\n'; // display val's new value return 0; } double &f() { return val; // return reference to val } Output:
Returning References from Functions #include double &change_it(int i); // return a reference double vals[] = {1.1, 2.2, 3.3, 4.4, 5.5}; int main() { int i; cout << "Here are the original values: "; for(i=0; i<5; i++) cout << vals[i] << ' '; cout << '\n'; change_it(1) = ; // change 2nd element change_it(3) = -98.8; // change 4th element cout << "Here are the changed values: "; for(i=0; i<5; i++) cout << vals[i] << ' '; cout << '\n'; return 0; } double &change_it(int i) { return vals[i]; // return a reference to the ith element } Output: (the changed values)
Returning References from Functions If a function returns a reference to a variable local to itself, what happens? int &f() { int i=10; return i; } i goes out of scope when f() returns. The behaviour of compilers is unpredictable in such cases. Some might give warning, some might give error. So be careful! One way to avoid is to return reference to a global, or to a variable that was passed to the function from the calling function
When to Use Reference Arguments We use them for two reasons: –To alter a data object in the calling function –To speed up a programme by not passing entire data object Second reason important for large data objects, such as structures and class objects When to pass by value, by pointer, by reference? Some guidelines: –If the data object is small, such as a built-in data type or a small structure, pass it by value –If the data object is an array, no choice but to use a pointer –If large data object is a structure, use a reference or pointer –If large data object is a class object, use a reference
Creating a Bounded Array To prevent array overrun #include int &put(int i); // put value into the // array int get(int i); // obtain a value from the // array int vals[10]; int error = -1; int main() { int i; put(0) = 10; // put values into the array put(1) = 20; put(9) = 30; cout << get(0) << ' '; cout << get(1) << ' '; cout << get(9) << ' '; // now, intentionally generate an error put(12) = 1; // Out of Bounds return 0; } // Put a value into the array. int &put(int i) { if(i>=0 && i<10) return vals[i]; // return a reference to // the ith element else { cout << "Bounds Error!\n"; return error; // return a reference to // error } } //________________________________ // Get a value from the array. int get(int i) { if(i>=0 && i<10) return vals[i]; // return the value of // the ith element else { cout << "Bounds Error!\n"; return error; // return an error }
Function Overloading C++ allows you to give two or more functions the same name. The function is said to be overloaded You can have a function average() that computes average of integers, another average() that calculates average of doubles How can we do that without ambiguity? Signature of a function is its list of parameters Overloaded functions must have different signatures Must differ in either the number, type, or order of arguments i.e. different signatures Compiler can differentiate then and calls the correct version of the function
Function Overloading Have you encountered overloading of some sort until now in C++ (not necessarily function overloading)? The arithmetic operators and The + operator is overloaded So, when to use overloaded functions? When same sort of operation is to be performed on different data sets Advantage: Overloading functions that perform closely related tasks can make programs more readable and understandable Also called function polymorphism
Function Overloading #include void f(int i); // integer parameter void f(int i, int j); // two integer parameters void f(double k); // one double parameter int main() { f(10); // call f(int) f(10, 20); // call f(int, int) f(12.23); // call f(double) return 0; } void f(int i) { cout << "In f(int), i is " << i << '\n'; } void f(int i, int j) { cout << "In f(int, int), i is " << i; cout << ", j is " << j << '\n'; } void f(double k) { cout << "In f(double), k is " << k << '\n'; }
Function Overloading Write overloaded functions, one of which averages three double numbers, and the other two doubles double ave(double n1, double n2) { return((n1 + n2)/2.0); } double ave(double n1, double n2, double n3) { return (n1 + n2 + n3)/3.0); } How about: int ave(double n1, double n2)... ? Syntax error. Return type not considered by compiler while differentiating between functions
Exercise Write three overloaded functions that take integer, double and long respectively, and return their absolute values #include int abs(int i); double abs(double d); long abs(long l); int main() { cout << abs(-10) << "\n"; cout << abs(-11.0) << "\n"; cout << abs(-9L) << "\n"; return 0; } int abs(int i) { cout << "using integer abs()\n"; if(i<0) return -i; else return i; } double abs(double d) { cout << "using double abs()\n"; if(d<0.0) return -d; else return d; } long abs(long l) { cout << "using long abs()\n"; if(l<0) return -l; else return l; }
Default Function Arguments A default value is a value that, although not universally applicable, is judged by the programmer to be appropriate in a majority of cases A default value frees the individual from having to attend to every small detail For example, in Unix each text file created by the author has read and write permissions for the author but only read permissions for all others In C++, we can give a parameter a default value in the function prototype. This value automatically used when no argument corresponding to that parameter is specified in a call to that function
Default Function Arguments Default arguments must be specified with the first occurrence of the function name, typically in the prototype (and where else?) void myfunc(double num = 0.0, char ch = 'X') {... } myfunc( , 'A'); // pass explicit values myfunc(10.1); // pass num a value, let ch default myfunc(); // let both num and ch default Omitted argument must be the rightmost. If not, also have to omit all those to its right. Similarly for signature. Call myfunc('A‘) is not correct. Also, the signature void myfunc(double num = 0.0, char ch) is not correct
Default Function Arguments #include // Calculate the volume of a box int boxVolume(int length = 1, int width = 1, int height = 1); int main() { cout << "The default box volume is: " << boxVolume() << endl << endl << "The volume of a box with length 10," << endl << "width 1 and height 1 is: " << boxVolume(10) << endl << endl << "The volume of a box with length 10," << endl << "width 5 and height 1 is: " << boxVolume(10, 5) << endl << endl << "The volume of a box with length 10," << endl << "width 5 and height 2 is: " << boxVolume(10, 5, 2) << endl; return 0; } int boxVolume(int length, int width, int height) { return length * width * height; } Output:
Function Overloading & Ambiguity Can have situations where compiler can not choose which function to run. Error Main cause is C++’s automatic type conversions: attempts to convert type of argument to the type of parameter double mpg(double miles, double gallons) { return (miles/gallons);} cout << mpg(45, 3) << “miles per gallon”; No error, as C++ converts 45 to 45.0 and 3 to 3.0 int myfunc(double d); cout << myfunc('c');//no error, conversion applied
Function Overloading & Ambiguity ff(char*, int); ff(int, int); ff(0, ‘a’); //matches ff(int, int) as 0 is an exact //match of int min(long, long); min(double, double); int i, j; min(i, j); //error: ambiguous, no ‘best’ match foo(int, int); foo(double, double); foo(‘a’, 3.14F); /* error; ambiguous: two ‘best’matches thru promotion, and no promotion is ‘preferred’ over the other */
Function Overloading & Ambiguity #include float myfunc(float i); double myfunc(double i); int main() { // unambiguous, calls myfunc(double) cout << myfunc(10.1) << " "; // ambiguous cout << myfunc(10); return 0; }... In C++, all decimal valued constants are of type double, unless specified to be float i.e. double by default
Default Arguments & Ambiguity #include void ff(int i); void ff(long, int = 0); int main() { ff(2L); //matches ff(long, 0) ff(0, 0); //matches ff(long, int) ff(0);//matches ff(int)as 0 of exact type int ff(3.14); /*error: ambiguous. Can match both functions*/ }... If void ff(double i) instead of ff(int i); ? Then ff(0) is ambiguous and ff(3.14) is ok
const Access Modifier Keyword const before a variable declaration makes the value of the variable unalterable const int change = 5; change += 1; //error const double PI = ;/*if pi oft-used in programme*/ circ_area = PI * r * r; Have to initialize and declare a const variable in the same line const int students; //error A common use of const is when we don’t want locations pointed to by pointers modified by mistake
Constant Pointers #include void code(const char *str);//location pointed to by str unalterable int main() { code("this is a test"); return 0; } void code(const char *str) { while(*str){ cout << (char) (*str+1); //print a as b and so on str++; } } The following won’t work void code(const char *str) { while(*str) { *str = *str + 1; // Error, can't modify location cout << (char) *str; str++; } }
Constant Array Passing #include void display(const int num[10]); // or display(const int *num); int main(){ int t[10],i; for(i=0; i<10; ++i) t[i]=i; display(t); // pass array t to a function cout <<endl; for(i=0; i<10; i++) cout << t[i] << ' '; //output? } // Print some numbers. void display( const int num[10]) { int i; for(i=0; i<10; i++) (num[i] = num[i] + 1);//ERROR }
Constant References // const references cannot be modified. #include using namespace std; void f(const int &i); int main() { int k = 10; f(k); return 0; } void f(const int &i) { i = 100; // Error, can't modify a const reference. cout << i; }