Б. Страуструп - Язык программирования С++ (1119446), страница 34
Текст из файла (страница 34)
Здесь "очевидное" решение простонепригодно:void print_mij(int m[][], int dim1, int dim2){for ( int i = 0; i<dim1; i++) {for ( int j = 0; j<dim2; j++)cout << ' ' << m[i][j];cout << '\n';}}// ошибкаВо-первых, описание параметра m[][] недопустимо, поскольку для вычисления адреса элементамногомерного массива нужно знать вторую размерность.
Во-вторых, выражение m[i][j] вычисляется как*(*(m+i)+j), а это, по всей видимости, не то, что имел в виду программист. Приведем правильноерешение:void print_mij(int** m, int dim1, int dim2){for (int i = 0; i< dim1; i++) {for (int j = 0; j<dim2; j++)cout << ' ' << ((int*)m)[i*dim2+j]; // запутаноcout << '\n';}}Выражение, используемое для выбора элемента матрицы, эквивалентно тому, которое создает дляэтой же цели транслятор, когда известна последняя размерность.
Можно ввести дополнительнуюпеременную, чтобы это выражение стало понятнее:int* v = (int*)m;// ...v[i*dim2+j]Лучше такие достаточно запутанные места в программе упрятывать. Можно определить типмногомерного массива с соответствующей операцией индексирования. Тогда пользователь может и незнать, как размещаются данные в массиве (см. упражнение 18 в $$7.13).4.6.6 Перегрузка имени функцииОбычно имеет смысл давать разным функциям разные имена. Если же несколько функций выполняетодно и то же действие над объектами разных типов, то удобнее дать одинаковые имена всем этимфункциям.
Перегрузкой имени называется его использование для обозначения разных операций надразными типами. Собственно уже для основных операций С++ применяется перегрузка. Действительно:для операций сложения есть только одно имя +, но оно используется для сложения и целых чисел, ичисел с плавающей точкой, и указателей. Такой подход легко можно распространить на операции,определенные пользователем, т.е. на функции. Например:void print(int);void print(const char*)// печать целого// печать строки символовДля транслятора в таких перегруженных функциях общее только одно - имя. Очевидно, по смыслу такиефункции сходны, но язык не способствует и не препятствует выделению перегруженных функций. Такимобразом, определение перегруженных функций служит, прежде всего, для удобства записи.
Но дляфункций с такими традиционными именами, как sqrt, print или open, нельзя этим удобством111Бьерн Страуструп.Язык программирования С++пренебрегать. Если само имя играет важную семантическую роль, например, в таких операциях, как + , *и << ($$7.2), или для конструктора класса ($$5.2.4 и $$7.3.1), то такое удобство становитсясущественным фактором. При вызове функции с именем f транслятор должен разобраться, какуюименно функцию f следует вызывать. Для этого сравниваются типы фактических параметров,указанные в вызове, с типами формальных параметров всех описаний функций с именем f. Врезультате вызывается та функция, у которой формальные параметры наилучшим образомсопоставились с параметрами вызова, или выдается ошибка если такой функции не нашлось.Например:void print(double);void print(long);void f(){print(1L);print(1.0);print(1);////////print(long)print(double)ошибка, неоднозначность: что вызыватьprint(long(1)) или print(double(1)) ?}Подробно правила сопоставления параметров описаны в $$R.13.2.
Здесь достаточно привести их суть.Правила применяются в следующем порядке по убыванию их приоритета:[1] Точное сопоставление: сопоставление произошло без всяких преобразований типа илитолько с неизбежными преобразованиями (например, имени массива в указатель, именифункции в указатель на функцию и типа T в const T).[2] Сопоставление с использованием стандартных целочисленных преобразований,определенных в $$R.4.1 (т.е. char в int, short в int и их беззнаковых двойников в int), а такжепреобразований float в double.[3] Сопоставление с использованием стандартных преобразований, определенных в $$R.4(например, int в double, derived* в base*, unsigned в int).[4] Сопоставление с использованием пользовательских преобразований ($$R.12.3).[5] Сопоставление с использованием эллипсиса ... в описании функции.Если найдены два сопоставления по самому приоритетному правилу, то вызов считаетсянеоднозначным, а значит ошибочным.
Эти правила сопоставления параметров работают с учетомправил преобразований числовых типов для С и С++. Пусть имеются такие описания функции print:voidvoidvoidvoidvoidprint(int);print(const char*);print(double);print(long);print(char);Тогда результаты следующих вызовов print() будут такими:void h(char c, int i, short s, float f){print(c);// точное сопоставление: вызывается print(char)print(i);// точное сопоставление: вызывается print(int)print(s);// стандартное целочисленное преобразование:// вызывается print(int)print(f);// стандартное преобразование:// вызывается print(double)print('a');// точное сопоставление: вызывается print(char)print(49);// точное сопоставление: вызывается print(int)print(0);// точное сопоставление: вызывается print(int)print("a");// точное сопоставление:// вызывается print(const char*)}112Бьерн Страуструп.Язык программирования С++Обращение print(0) приводит к вызову print(int), ведь 0 имеет тип int.
Обращение print('a') приводит квызову print(char), т.к. 'a' - типа char ($$R.2.5.2).Отметим, что на разрешение неопределенности при перегрузке не влияет порядок описанийрассматриваемых функций, а типы возвращаемых функциями значений вообще не учитываются.Исходя из этих правил можно гарантировать, что если эффективность или точность вычисленийзначительно различаются для рассматриваемых типов, то вызывается функция, реализующая самыйпростой алгоритм.
Например:int pow(int, int);double pow(double, double);// из <math.h>complex pow(double, complex);// из <complex.h>complex pow(complex, int);complex pow(complex, double);complex pow(complex, complex);void k(complex z){int i = pow(2,2);// вызывается pow(int,int)double d = pow(2.0,2); // вызывается pow(double,double)complex z2 = pow(2,z); // вызывается pow(double,complex)complex z3 = pow(z,2); // вызывается pow(complex,int)complex z4 = pow(z,z); // вызывается pow(complex,complex)}4.6.7 Стандартные значения параметровВ общем случае у функции может быть больше параметров, чем в самых простых и наиболее частоиспользуемых случаях. В частности, это свойственно функциям, строящим объекты (например,конструкторам, см.
$$5.2.4). Для более гибкого использования этих функций иногда применяютсянеобязательные параметры. Рассмотрим в качестве примера функцию печати целого числа. Вполнеразумно применить в качестве необязательного параметра основание счисления печатаемого числа,хотя в большинстве случаев числа будут печататься как десятичные целые значения. Следующаяфункцияvoid print (int value, int base =10);void F(){print(31);print(31,10);print(31,16);print(31,2);}напечатает такие числа:31 31 1f 11111Вместо стандартного значения параметра можно было бы использовать перегрузку функции print:void print(int value, int base);inline void print(int value) { print(value,10); }Однако в последнем варианте текст программы не столь явно демонстрирует желание иметь однуфункцию print, но при этом обеспечить удобную и краткую форму записи.Тип стандартного параметра сверяется с типом указанного значения при трансляции описания функции,а значение этого параметра вычисляется в момент вызова функции.
Задавать стандартное значениеможно только для завершающих подряд идущих параметров:int f(int, int =0, char* =0);int g(int =0, int =0, char*);int h(int =0, int, char* =0);// нормально// ошибка// ошибка113Бьерн Страуструп.Язык программирования С++Отметим, что в данном контексте наличие пробела между символами * и = весьма существенно,поскольку *= является операцией присваивания:int nasty(char*=0);// синтаксическая ошибка4.6.8 Неопределенное число параметровСуществуют функции, в описании которых невозможно указать число и типы всех допустимыхпараметров.
Тогда список формальных параметров завершается эллипсисом (...), что означает: "и,возможно, еще несколько аргументов". Например:int printf(const char* ...);При вызове printf обязательно должен быть указан параметр типа char*, однако могут быть (а могут и небыть) еще другие параметры. Например:printf("Hello, world\n");printf("My name is %s %s\n", first_name, second_name);printf("%d + %d = %d\n", 2,3,5);Такие функции пользуются для распознавания своих фактических параметров недоступной трансляторуинформацией. В случае функции printf первый параметр является строкой, специфицирующей форматвывода.