Актуальные вопросы в работе с С++
Актуальные вопросы в работе с С++
Содержание Введение 1.Написать программу 2. Что такое константа? Приведите примеры типизированных и не типизированных констант 3. Приведите полный список базовых целочисленных типов и занимаемый ими размер памяти в байтах 4. Что такое указатель? Как по указателю можно занести значение? Приведите примеры 5. Что такое функция? Можно ли возвращать значения из функции через переданные ей аргументы? Если можно, то как? Приведите примеры 6. Что такое класс? В чем отличие класса от структуры? Приведите примеры структуры и класса 7. Что такое виртуальная функция? Приведите пример. В каких случаях используются виртуальные функции? Литература Введение Язык программирования решает две взаимосвязанные задачи: позволяет программисту записать подлежащие выполнению действия и формирует понятия, которыми программист оперирует, размышляя о своей задаче. Первой цели идеально отвечает язык, который очень "близок машине". Тогда со всеми ее основными "сущностями" можно просто и эффективно работать на этом языке, причем делая это очевидным для программиста способом. Именно это имели в виду создатели С. Второй цели идеально отвечает язык, который настолько "близок к поставленной задаче", что на нем непосредственно и точно выражаются понятия, используемые в решении задачи. Именно это имелось в виду, когда первоначально определялись средства, добавляемые к С. Связь между языком, на котором мы думаем и программируем, а также между задачами и их решениями, которые можно представить в своем воображении, довольно близка. По этой причине ограничивать возможности языка только поиском ошибок программиста - в лучшем случае опасно. Как и в случае естественных языков, очень полезно обладать, по крайней мере, двуязычием. Язык предоставляет программисту некоторые понятия в виде языковых инструментов; если они не подходят для задачи, их просто игнорируют. Например, если существенно ограничить понятие указателя, то программист будет вынужден для создания структур, указателей и т.п. использовать вектора и операции с целыми. Хороший проект программы и отсутствие в ней ошибок нельзя гарантировать только наличием или отсутствием определенных возможностей в языке. Действительно, понятие класса в С++ проявило себя как мощное концептуальное средство. Название С++ (си плюс плюс) , было придумано Риком Маскитти летом 1983 г. Это название отражает эволюционный характер изменений языка С. Обозначение ++ относится к операции наращивания С. Чуть более короткое имя С+ является синтаксической ошибкой. Кроме того, оно уже было использовано как название совсем другого языка. Выбор С в качестве базового языка для С++ объясняется следующими его достоинствами: (1) универсальность, краткость и относительно низкий уровень; (2) адекватность большинству задач системного программирования; (3) он идет в любой системе и на любой машине; (4) полностью подходит для программной среды UNIX. Язык программирования С++ задумывался как язык, который будет: - лучше языка С; - поддерживать абстракцию данных; - поддерживать объектно-ориентированное программирование. Цель работы - ознакомится с основными терминами С++ и их значениями, научиться составлять программы. 1. Написать программу Которая: - получает из стандартного потока ввода строку, содержащую число прописью (напр. « двадцать три»); - подсчитывает и выводит на экран количество символов в строке и их средний код; - переводит введенную строку в число (напр. соответственно 23) и выводит на экран. // sp_kr.cpp : контрольная работа // #include "stdafx.h" #include <string.h> #include <iostream> using namespace std; int main( void ) { printf("rabota s chislami \n\n"); // ввод и инициализация cout << "Vvedite stroku (dvadcat` tri)" << endl; char stroka[80] = "сто двадцать три"; //gets(stroka); // исходная строка char razd[] = " ,\t\n"; // множество разделителей char *slovo; // количество символов в строке и их средний код int kol_c= 0; int kod_c= 0; for (int j = 0; j<strlen(stroka); j++) { kod_c += putchar(stroka[j]);//код символа kol_c ++; } cout << endl << "Kolichestvo simvolov v stroke =" << kol_c << endl; cout << "Srednij kod =" << kod_c/kol_c << endl; // выдедение слова slovo = strtok(stroka, razd); int chislo= 0; while(slovo != NULL) { // обработка слов if (stricmp(slovo, "один")==0) { chislo += 1;} if (stricmp(slovo, "два")==0) {chislo += 2;} if (stricmp(slovo, "три")==0) {chislo += 3;} if (stricmp(slovo, "четыре")==0) {chislo += 4;} if (stricmp(slovo, "пять")==0) {chislo += 5;} if (stricmp(slovo, "шесть")==0) {chislo += 6;} if (stricmp(slovo, "семь")==0) {chislo += 7;} if (stricmp(slovo, "восемь")==0) {chislo += 8;} if (stricmp(slovo, "девять")==0) {chislo += 9;} if (stricmp(slovo, "десять")==0) {chislo += 10;} if (stricmp(slovo, "одиннадцать")==0) {chislo += 11;} if (stricmp(slovo, "двенадцать")==0) {chislo += 12;} if (stricmp(slovo, "тринадцать")==0) {chislo += 13;} if (stricmp(slovo, "четырнадцать")==0) {chislo += 14;} if (stricmp(slovo, "пятнадцать")==0) {chislo += 15;} if (stricmp(slovo, "шестнадцать")==0) {chislo += 16;} if (stricmp(slovo, "семнадцать")==0) {chislo += 17;} if (stricmp(slovo, "восемнадцать")==0) {chislo += 18;} if (stricmp(slovo, "девятнадцать")==0) {chislo += 19;} if (stricmp(slovo, "двадцать")==0) {chislo += 20;} if (stricmp(slovo, "тридцать")==0) {chislo += 30;} if (stricmp(slovo, "сорок")==0) {chislo += 40;} if (stricmp(slovo, "пятьдесят")==0) {chislo += 50;} if (stricmp(slovo, "шестьдесят")==0) {chislo += 60;} if (stricmp(slovo, "семьдесят")==0) {chislo += 70;} if (stricmp(slovo, "восемьдесят")==0) {chislo += 80;} if (stricmp(slovo, "девяносто")==0) {chislo += 90;} if (stricmp(slovo, "сто")==0) {chislo += 100;} if (stricmp(slovo, "двести")==0) {chislo += 200;} if (stricmp(slovo, "триста")==0) {chislo += 300;} if (stricmp(slovo, "четыреста")==0) {chislo += 400;} if (stricmp(slovo, "пятьсот")==0) {chislo += 500;} if (stricmp(slovo, "шестьсот")==0) {chislo += 600;} if (stricmp(slovo, "семьсот")==0) {chislo += 700;} if (stricmp(slovo, "восемьсот")==0) {chislo += 800;} if (stricmp(slovo, "девятьсот")==0) {chislo += 900;} //printf(" %s\n", slovo); // выделение очередного слова slovo = strtok(NULL, razd); } cout << "Chislo =" << chislo << endl; gets(stroka); return 0; } 2. Что такое константа? Приведите примеры типизированных и не типизированных констант C++ дает возможность записи значений основных типов: символьных констант, целых констант и констант с плавающей точкой. Кроме того, ноль (0) может использоваться как константа любого указательного типа, и символьные строки являются константами типа char. Можно также задавать символические константы. Символическая константа - это имя, значение которого не может быть изменено в его области видимости. В C++ имеется три вида символических констант: (1) любому значению любого типа можно дать имя и использовать его как константу, добавив к его описанию ключевое слово const; (2) множество целых констант может быть определено как перечисление; и (3) любое имя вектора или функции является константой. Типизированные константы можно сравнить с инициализированными переменными - переменными, значения которых определяются на входе в их блок. В отличие от нетипизированных констант в описании типизированной константы указывается как тип, так и значение константы. описание типизированной константы L->¦идентификатор+->¦ : +->¦тип+->¦ = +->¦типизированная+--> константа ¦ Таблица 1 - Константа Типизированные константы можно использовать точно так же, как переменные того же самого типа, и они указываются в левой части оператора присваивания. Отметим, что типизированные константы инициализируются только один раз - в начале выполнения программы. Таким образом, при каждом новом входе в процедуру или функцию локально описанные типизированные константы заново не инициализируются. Кроме обычных выражений-констант значение типизированной константы может задаваться с помощью адресного выражения-константы. Адресное выражение-константа - это выражение, предусматривающее получение адреса, смещения или сегмента глобальной переменной, типизированной константы, процедуры или функции. Адресные выражения-константы не могут ссылаться на локальные переменные (расположенные в стеке) или динамические переменные (размещенные в динамически распределяемой области памяти), поскольку их адреса нельзя вычислить на этапе компиляции. Константы простого типа. Описание типизированной константы с простым типом означает указание значения константы: Const Maximum : integer = 9999; Factor : real = -0.1; Breakchar : char = #3; Значение типизированной константы можно задать с помощью адресного выражение-константы, то есть выражения, в котором используются адрес, смещение или сегмент глобальной переменной, типизированной константы, процедуры или функции. Например: var Buffer: array[0..1023] of Byte; const BufferOfs: Word = Ofs(Buffer); BufferSeg: Word = Seg(Buffer); Поскольку типизированная константа фактически представляет собой переменную со значением константы, она не является взаимозаменяемой для обычных констант. Например, она не может использоваться в описании других констант или типов. const Min : integer = 0; Max : integer = 99; type Vector = array[Min..Max] of integer; Описание Vector является недопустимым, поскольку Min и Max являются типизированными константами. Константы строкового типа Описание типизированной константы строкового типа содержит максимальную длину строки и ее начальное значение: const Heading : string[7] = 'Section'; NewLine : string[2] = #13#10; TrueStr : string[5] = 'Yes'; FalseStr : string[5] = 'No'; Константы структурного типа Описание константы структурного типа определяет значение каждого компонента структуры. Поддерживает описания констант типа массив, запись, множество и указатель. Константы файлового типа и константы типа массив или запись, содержащие компоненты файлового типа, не допускаются. Константы типа массив. Описание константы типа массив содержит значения элементов, заключенные в скобки и разделенные запятыми. Приведем пример константы типа массив: type Status = (Active,Passive,Waiting); StatusMap = array[Status] of string[7]; const StatStr: StatusMap = ('Active','Passive','Waiting'); В этом примере определяется константа-массив StarStr, которая может использоваться для преобразования значений типа Status в соответствующие им строковые представления. Элементами массива StarStr являются: StatStr[Active] = 'Active' StatStr[Passive] = 'Passive' StatStr[Waiting] = 'Waiting' Тип элемента константы-массива может быть любым, кроме файлового типа. Упакованные константы строкового типа (символьные массивы) могут быть определены и как одиночные символы, и как строки. Определение: const Digits:array[0..9] of char=('0','1','2','3','4','5','6','7','8','9'); можно представить в более удобном виде: const Digits: array[0..9] of char = '0123456789'; При разрешении расширенного синтаксиса (с помощью директивы компилятора {$X+}) массивы с нулевой базой могут инициализироваться строкой, которая короче, чем описанная длина массива, например: const FileName = array[0..79] of Char = 'TEXT.PAS'; В таких случаях оставшиеся символы устанавливаются в NULL (#0), и массив содержит строку с завершающим нулем. При описании константы типа "многомерный массив" константы каждой размерности заключаются в отдельные скобки и разделяются запятыми. Расположенные в середине константы соответствуют самым правым размерностям. Описание: type Cube = array[0..1,0..1,0..1] of integer; const Maze: Cube = (((0,1),(2,3)),((4,5),(6,7))); задает следующие начальные значения массива Maze: Maze[0, 0, 0] = 0 Maze[0, 0, 1] = 1 Maze[0, 1, 0] = 2 Maze[0, 1, 1] = 3 Maze[1, 0, 0] = 4 Maze[1, 0, 1] = 5 Maze[1, 1, 0] = 6 Maze[1, 1, 1] = 7 Константы типа запись Описание константы типа запись содержит идентификатор и значение каждого поля, заключенные в скобки и разделенные точками с запятой. Приведем несколько примеров констант-записей: type Point = record x,y: real; end; Vector = array[0..1] of Point; Month = (Jan,Feb,Mar,Apr,May,Jun,Jly,Aug,Sep,Oct,Nov,Dec); Date = record d: 1..31; m: Month; y: 1900..1999; end; const Origin : Point = (x: 0.0; y: 0.0); Line : Vector = ((x: -3.1; y: 1.5),(x: 5.8; y: 3.0)); SomeDay : Date = (d: 2; m: Dec; y: 1960); Поля должны указываться в том же порядке, как они следуют в описании типа запись. Если запись содержит поля файлового типа, то для этого типа запись нельзя описать константу. Если запись содержит вариант, то можно указывать только поля выбранного варианта. Если вариант содержит поле признака, то его значение должно быть определено. Константы объектного типа При описании константы объектного типа используется тот же синтаксис, что и при описании константы типа запись. Значения для элементов (компонентов) метода задаваться не могут. С учетом приводимых ранее описаний объектных типов, приведем некоторые примеры констант объектного типа: const ZeroPoint: Point = (X: 0; Y: 0) ScreenRect: Rect = (A: (X: 0; Y: 0); B: (X: 80; Y: 25); CountField: NumField = (X: 5; Y: 20; Len: 4; Name: nil; Value: 0; Min: -999; Max: 999); Константы объектного типа, которые содержат виртуальные методы, не требуется инициализировать с помощью вызова конструктора. Эта инициализация автоматически выполняется компилятором. Константы множественного типа Описание константы множественного типа может содержать несколько элементов, заключенных в квадратные скобки и разделенных запятыми. Каждый элемент такой константы представляет собой константу или отрезок типа, состоящий из двух констант, разделенных двумя точками. Приведем несколько примеров констант-множеств: type Digits = set of 0..9; Letters = set of 'A'..'Z'; const EvenDigits: Digits = [0,2,4,6,8]; Vowels : Letters = ['A','E','I','O','U','Y']; HexDigits : set of '0'..'z' = ['0'..'9','A'..'F','a'..'f']; Константы ссылочного типа Описание константы ссылочного типа может содержать только значение nil (пусто). Приведем несколько примеров type TDirection = (Left, Right, Up, Down); TStringPtr = ^String; TNodePtr = ^Node; TNode = record Next: NodePtr; Symbol: StringPtr; Value: Direction; end; const S1: string[4] = 'DOWN'; S2: string[2] = 'UP'; S3: string[5] = 'RIGHT'; S4: string[4] = 'LEFT'; N1: Node = (Next: nil; Symbol: @S1; Value: Down); N2: Node = (Next: @N1; Symbol: @S2; Value: Up); N3: Node = (Next: @N2; Symbol: @S3; Value: Right); N2: Node = (Next: @N3; Symbol: @S4; Value: Left); DirectionTable: NodePtr = @N4; Если разрешен расширенный синтаксис (указана директива компилятора {$X+}), типизированная константа типа PChar может инициализироваться строковой константой, например: const Message: PChar = 'Программа завершена'; Prompt: PChar = 'Введите значения: '; Digits: array[0..9] of PChar = ( 'Ноль', 'Один', 'Два', 'Три', 'Четыре', 'Пять', 'Шесть', 'Семь', 'Восемь', 'Девять'); Результатом будет то, что указатель теперь указывает на область памяти, содержащую копию строкового литерала с завершающим нулем. Константы процедурного типа Константы процедурного типа должны определять идентификатор процедуры или функции, совместимый по присваиванию с типом константы. Приведем следующий пример: type ErrorProc = procedure(ErrorCode: Integer); procedure DefaultError(ErrorCode: Integer); far; begin WriteLn('Error ', ErrorCode, '.'); end; const ErrorHandler: ErrorProc = DefaultError; 3. Приведите полный список базовых целочисленных типов и занимаемый ими размер памяти в байтах Целочисленные типы - обозначают множества целых чисел в различных диапазонах. Имеется пять целочисленных типов, различающихся диапазоном допустимых значений и размером занимаемой оперативной памяти. Целочисленные типы обозначаются идентификаторами: Byte, ShortInt, Word, Integer, LongInt; их характеристики приведены в следующей таблице. Таблица 1 - Целочисленные типы |
Тип | Диапазон | Размер в байтах | | Byte ShortInt Word Integer LongInt | 0 ... 255 -128 ... 127 0 ... 65535 -32768 ... 32767 -2147483648 ... 2147483647 | 1 1 2 2 4 | | |
Значения целых типов записываются в программе привычным способом: 123 4 -3 +345 -699 Наличие десятичной точки в записи целого числа недопустимо. Будет ошибкой записать целое число следующим образом: 123.0 Кроме привычной десятичной формы записи допускается запись целых чисел в шестнадцатиричном формате, используя префикс $, например: $01AF $FF $1A $F0A1B Регистр букв A,B, ..., F значения не имеет. Допустимые операции: - присваивание; - все арифметические: +, - ,*, /, div, mod (при обычном делении [/] результат вещественный!); - сравнение <, >, >=, <=, <>, =. 4. Что такое указатель? Как по указателю можно занести значение? Приведите примеры Специальными объектами в программах на языках Си++ являются указатели. Различают указатели-переменные (именно их мы будем называть указателями) и указатели-константы. Значениями указателей служат адреса участков памяти, выделенных для объектов конкретных типов: именно поэтому в определении и описании указателя всегда присутствует обозначение соответствующего ему типа. Эта информация позволяет в последующем с помощью указателя получить доступ ко всему сохраняемому объекту в целом. Указатели делятся на две категории - указатели на объекты и указатели на функции. Выделение этих двух категорий связано с отличиями в свойствах и правилах использования. Например, указатели ФУНКЦИЙ не допускают применения к ним арифметических операций, а указатели объектов разрешено использовать в некоторых арифметических выражениях. Начнем с указателей объектов. В простейшем случае определение и описание указателя-переменной на некоторый объект имеют вид: tуре *имя_ указателя; где tуре _ обозначение типа; имя_указателя - это идентификатор; * - унитарная операция раскрытия ссылки (операция разыменования; операция обращения по адресу; операция доступа по адресу), операндом которой должен быть указатель (именно в соответствии с этим правилом вслед за ней следует имя_указателя). Признаком указателя при лексическом разборе определения или описания служит символ '*', помещенный перед именем. Таким образом, при необходимости определить несколько указателей на объекты одного и того же типа этот символ '*' помещают перед каждым именем. Например, определение int *i1р, *i2р, *iЗр, i; вводит три указателя на объекты целого типа i1р, i2р, i3р и одну переменную i целого типа. Переменной i будет отведено в памяти 2 байта (ТС++ или ВС++), а указатели i1р, i2р, i3р разместятся в участках памяти, размер которых также зависит от реализации, но которые только иногда имеют длину 2 байта. В совокупности имя типа и символ '*' перед именем воспринимаются как обозначение особого типа данных "указатель на объект данного типа". При определении указателя в большинстве случаев целесообразно выполнить его инициализацию. Формат определения станет таким: tуре *имя_ указателя инициализатор; Как упоминалось, инициализатор имеет две формы записи, поэтому допустимы следующие две формы определения указателей: tyре *имя_указателя = инициализирующее_выражение; tурe *имя_указателя (инициализирующее_выражение); В качестве инициализирующего_выражения должно использоваться константное выражение, частными случаями которого являются: - явно заданный адрес участка памяти; - указатель, уже имеющий значение; - выражение, позволяющее получить адрес объекта с помощью операции '&'. Если значение константного выражения равно нулю, то это нулевое значение преобразуется к пустому (иначе нулевому) указателю. Синтаксис языка "гарантирует, что этот указатель отличен от указателя на любой объект". Кроме того, внутреннее (битовое) представление пустого указателя может отличаться от битового представления целого значения 0. В компиляторах ТС++ и ВС++ условное нулевое значение адреса, соответствующее значению пустого указателя, имеет специальное обозначение NULL. Примеры определений указателей: сhаr cc = 'd'; // Символьная переменная (типа сhаr) сhаr *рс = &cс; // Инициализированный указатель на объект // типа сhаr сhаr *рtr(NULL); // Нулевой указатель на объект типа сhаr сhаr *р; // Неинициализированный указатель на // объект типа сhаr 5. Что такое функция? Можно ли возвращать значения из функции через переданные ей аргументы? Если можно, то как? Приведите примеры Для чего нужны функции? Чтобы ответить на этот вопрос, нужно понять, что вообще представляют собой функции. В программировании, как и в математике, функция есть отображение множества ее аргументов на множество ее значений. То есть функция для каждого набора значений аргумента возвращает какие-то значения, являющиеся результатом ее работы. Зачем нужны функции, попытаемся объяснить на примере. Классический пример функции в программировании - это функция, вычисляющая значение факториала числа. То есть мы задаем ей число, а она возвращает нам его факториал. При этом не нужно для каждого числа, факториал которого мы хотим получить, повторять один и тот же код - достаточно просто вызвать функцию с аргументом, равным этому числу. Функция вычисления факториала натурального числа <?php function fact($n){ if ($n==0) return 1; else return $fact = $n * fact($n-1); } echo fact(3); // можно было бы написать echo (3*2); // но если число большое, echo fact(50); // то удобнее пользоваться функцией, // чем писать echo (50*49*48*...*3*2); ?> Таким образом, когда мы осуществляем действия, в которых прослеживается зависимость от каких-то данных, и при этом, возможно, нам понадобится выполнять такие же действия, но с другими исходными данными, удобно использовать механизм функций - оформить блок действий в виде тела функции, а меняющиеся данные - в качестве ее параметров. Посмотрим, как в общем виде выглядит задание (объявление) функции. Функция может быть определена с помощью следующего синтаксиса: function Имя_функции (параметр1, параметр2, ... параметрN){ Блок_действий return "значение возвращаемое функцией"; } Если прямо так написать в php-программе, то работать ничего не будет. Во-первых, Имя_функции и имена параметров функции (параметр1, параметр2 и т.д.) должны соответствовать правилам наименования в PHP (и русских символов в них лучше не использовать). Имена функций нечувствительны к регистру. Во-вторых, параметры функции - это переменные языка, поэтому перед названием каждого из них должен стоять знак $. Никаких троеточий ставить в списке параметров нельзя. В-третьих, вместо слов блок_действий в теле функции должен находиться любой правильный PHP-код (не обязательно зависящий от параметров). И наконец, после ключевого слова return должно идти корректное php-выражение (что-либо, что имеет значение). Кроме того, у функции может и не быть параметров, как и возвращаемого значения. Пример правильного объявления функции - функция вычисления факториала, приведенная выше. Как происходит вызов функции? Указывается имя функции и в круглых скобках список значений ее параметров, если таковые имеются: <?php Имя_функции ("значение_для_параметра1", "значение_для_параметра2",...); // пример вызова функции - вызов функции // вычисления факториала приведен выше, // там для вычисления факториала числа 3 // мы писали: fact(3); // где fact - имя вызываемой функции, // а 3 - значение ее параметра с именем $n ?> Когда можно вызывать функцию? Казалось бы, странный вопрос. Функцию можно вызвать после ее определения, т.е. в любой строке программы ниже блока function f_name(){...}. В PHP3 это было действительно так. Но уже в PHP4 такого требования нет. Все дело в том, как интерпретатор обрабатывает получаемый код. Единственное исключение составляют функции, определяемые условно (внутри условных операторов или других функций). Когда функция определяется таким образом, ее определение должно предшествовать ее вызову. <? $make = true; /* здесь нельзя вызвать Make_event(); потому что она еще не существует, но можно вызвать Save_info() */ Save_info("Вася","Иванов", "Я выбрал курс по PHP"); if ($make){ // определение функции Make_event() function Make_event(){ echo "<p>Хочу изучать Python<br>"; } } // теперь можно вызывать Make_event() Make_event(); // определение функции Save_info function Save_info($first, $last, $message){ echo "<br>$message<br>"; echo "Имя: ". $first . " ". $last . "<br>"; } Save_info("Федя","Федоров", "А я выбрал Lisp"); // Save_info можно вызывать и здесь ?> Пример 5.1. Определение функции внутри условного оператора Если функция однажды определена в программе, то переопределить или удалить ее позже нельзя. Несмотря на то, что имена функций нечувствительны к регистру, лучше вызывать функцию по тому же имени, каким она была задана в определении. <?php /* нельзя сохранить данные, т.е. вызвать функцию DataSave() до того, как выполнена проверка их правильности, т.е. вызвана функция DataCheck() */ DataCheck(); DataSave(); function DataCheck(){ // проверка правильности данных function DataSave(){ // сохраняем данные } } ?> Пример 5.2. Определение функции внутри функции Рассмотрим подробнее аргументы функций, их назначение и использование. Аргументы функций У каждой функции может быть, как мы уже говорили, список аргументов. С помощью этих аргументов в функцию передается различная информация (например, значение числа, факториал которого надо подсчитать). Каждый аргумент представляет собой переменную или константу. С помощью аргументов данные в функцию можно передавать тремя различными способами. Это передача аргументов по значению (используется по умолчанию), по ссылке и задание значения аргументов по умолчанию. Рассмотрим эти способы подробнее. Когда аргумент передается в функцию по значению, изменение значения аргумента внутри функции не влияет на его значение вне функции. Чтобы позволить функции изменять ее аргументы, их нужно передавать по ссылке. Для этого в определении функции перед именем аргумента следует написать знак амперсанд «&». <?php // напишем функцию, которая бы добавляла // к строке слово checked function add_label(&$data_str){ $data_str .= "checked"; } $str = "<input type=radio name=article "; // пусть имеется такая строка echo $str ."><br>"; // выведет элемент формы - // не отмеченную радио кнопку add_label($str); // вызовем функцию echo $str ."><br>"; // это выведет уже отмеченную // радио кнопку ?> Пример 5.3. Передача аргументов по ссылке В функции можно определять значения аргументов, используемые по умолчанию. Само значение по умолчанию должно быть константным выражением, а не переменной и не представителем класса или вызовом другой функции. У нас есть функция, создающая информационное сообщение, подпись к которому меняется в зависимости от значения переданного ей параметра. Если значение параметра не задано, то используется подпись "Оргкомитет". <?php function Message($sign="Оргкомитет"){ // здесь параметр sign имеет по умолчанию // значение "Оргкомитет" echo "Следующее собрание состоится завтра."; echo "$sign .<br>"; } Message(); // вызываем функцию без параметра. // В этом случае подпись - это Оргкомитет Message("С уважением, Вася"); // В этом случае подпись // будет "С уважением, Вася" ?> Пример 5.4. Значения аргументов по умолчанию Результатом работы этого скрипта будет: Следующее собрание состоится завтра. Оргкомитет. Следующее собрание состоится завтра. С уважением, Вася. Если у функции несколько параметров, то те аргументы, для которых задаются значения по умолчанию, должны быть записаны после всех остальных аргументов в определении функции. В противном случае появится ошибка, если эти аргументы будут опущены при вызове функции. Например, мы хотим внести описание статьи в каталог. Пользователь должен ввести такие характеристики статьи, как ее название, автор и краткое описание. Если пользователь не вводит имя автора статьи, считаем, что это Иванов Иван. <?php function Add_article($title, $description, $author="Иванов Иван"){ echo "Заносим в каталог статью: $title,"; echo "автор $author"; echo "<br>Краткое описание: "; echo "$description <hr>"; } Add_article("Информатика и мы", "Это статья про информатику ...", "Петров Петр"); Add_article("Кто такие хакеры", "Это статья про хакеров ..."); ?> В результате работы скрипта получим следующее Заносим в каталог статью: Информатика и мы, автор Петров Петр. Краткое описание: Это статья про информатику... Заносим в каталог статью: Кто такие хакеры, автор Иванов Иван. Краткое описание: Это статья про хакеров... Если же мы напишем вот так: <?php function Add_article($author="Иванов Иван", $title, $description){ // ...действия как в предыдущем примере } Add_article("Кто такие хакеры", "Это статья про хакеров..."); ?> То в результате получим: Warning: Missing argument 3 for add_article() in c:\users\nina\tasks\func\def_bad.php on line 2 Возвращаемые значения Все функции, приведенные выше в качестве примеров, выполняли какие-либо действия. Кроме подобных действий, любая функция может возвращать как результат своей работы какое-нибудь значение. Это делается с помощью утверждения return. Возвращаемое значение может быть любого типа, включая списки и объекты. Когда интерпретатор встречает команду return в теле функции, он немедленно прекращает ее исполнение и переходит на ту строку, из которой была вызвана функция. Например, составим функцию, которая возвращает возраст человека. Если человек не умер, то возраст считается относительно текущего года. <?php /* если второй параметр вычисляется как true, то он рассматривается как дата смерти, */ function Age($birth, $is_dead){ if ($is_dead) return $is_dead-$birth; else return date("Y")-$birth; } echo Age(1971, false); // выведет 33 echo Age(1971, 2001); // выведет 30 ?> В этом пример можно было и не использовать функцию return, а просто заменить ее функцией вывода echo. Однако если мы все же делаем так, что функция возвращает какое-то значение (в данном случае возраст человека), то в программе мы можем присвоить любой переменной значение этой функции: $my_age = Age(1981, 2004); В результате работы функции может быть возвращено только одно значение. Несколько значений можно получить, если возвращать список значений (одномерный массив). Допустим, мы хотим получить полный возраст человека с точностью до дня. <?php function Full_age($b_day, $b_month, $b_year){ if (date("m")>$b_month && date("d")>$b_day) { $day = date ("d") - $b_day; $month = date("m") - $b_month; $year = date("Y") - $b_year; } else { $year = date("Y") - $b_year - 1; $day = 31 - ($b_day - date ("d")); $month = 12 - ($b_month - date("m")); } return array ($day,$month,$year); } $age = Full_age("07","08","1974"); echo "Вам $age[2] лет, $age[1] месяцев и $age[0] дней"; // выведет "Вам 29 лет, 11 месяцев и 5 дней" ?> Когда функция возвращает несколько значений для их обработки в программе, удобно использовать языковую конструкцию list(), которая позволяет одним действием присвоить значения сразу нескольким переменным. Например, в предыдущем примере, оставив без изменения функцию, обработать возвращаемые ей значения можно было так: <? // задание функции Full_age() list($day,$month,$year) = Full_age("07", "08","1974"); echo "Вам $year лет, $month месяцев и $day дней"; ?> Вообще конструкцию list() можно использовать для присвоения переменным значений элементов любого массива. <? $arr = array("first","second"); list($a,$b) = $arr; // переменной $a присваивается первое // значение массива, $b - второе echo $a," ",$b; // выведет строку «first second» ?> Пример 5.9. Использование list() Возвращение ссылки В результате своей работы функция также может возвращать ссылку на какую-либо переменную. Это может пригодиться, если требуется использовать функцию для того, чтобы определить, какой переменной должна быть присвоена ссылка. Чтобы получить из функции ссылку, нужно при объявлении перед ее именем написать знак амперсанд (&) и каждый раз при вызове функции перед ее именем тоже писать амперсанд (&). Обычно функция возвращает ссылку на какую-либо глобальную переменную (или ее часть - ссылку на элемент глобального массива), ссылку на статическую переменную (или ее часть) или ссылку на один из аргументов, если он был также передан по ссылке. <? $a = 3; $b = 2; function & ref($par){ global $a, $b; if ($par % 2 == 0) return $b; else return $a; } $var =& ref(4); echo $var, " и ", $b, "<br>"; //выведет 2 и 2 $b = 10; echo $var, " и ", $b, "<br>"; // выведет 10 и 10 ?> Пример 5.10. Возвращение ссылки При использовании синтаксиса ссылок в переменную $var нашего примера не копируется значение переменной $b возвращенной функцией $ref, а создается ссылка на эту переменную. То есть теперь переменные $var и $b идентичны и будут изменяться одновременно. 6. Что такое класс? В чем отличие класса от структуры? Приведите примеры структуры и класса Для С++ классы System.Object, System.Exception, System.File-Stream и System.Random -- это ссылочные типы (память выделяется из упр. кучи). В свою очередь размерные типы в документации называются структурами (structure) и перечислениями (enumeration). Например, структуры System.In132, System.Boolean, System.Decimal, System.TimeSpan и перечисления System.DayOfWeek, System.10.FileAttributes и System.Drawing.FontStyle являются размерными типами (хранятся обычно в стеке потока, но могут быть встроены в ссылочные типы). Переменные размерного типа непосредственно содержат данные, а переменные ссылочного типа содержат ссылку на область памяти, содержащую данные. При этом, память под ссылочные переменные всегда выделяется в куче, а память под размерные, обычно, в стеке. Как известно, в .NET для освобождения неиспользуемой памяти в куче используется сборщик мусора. Особенностью стека является то, что память в нем освобождается автоматически (без каких-либо накладных расходов). Таким образом, уничтожение ссылочных объектов путем сборки мусора менее эффективно, чем размерных. Другой важный момент - это упаковка (boxing) и распаковка (unboxing). При упаковке размерного типа происходит выделение области памяти в куче и копирование значения размерного типа в эту область. Упакованный размерный тип обладает свойствами ссылочного. Распаковка - обратный процесс, в результате которого упакованный размерный тип копируется на стек. Благодаря упаковке любой размерный тип может интерпретироваться как ссылочный и, как следствие этого, любой размерный тип может использоваться вместо object. Важно понимать, что упаковка и распаковка требуют дополнительных затрат памяти и времени. Поэтому следует избегать этих операций в большом количестве. Теперь вернемся к нашему вопросу. Отличие структуры от класса: - Структура является размерным типом, а класс - ссылочным. - Все структурные типы неявно наследуются от System.ValueType, они не бывают абстрактными и всегда неявно запечатаны (sealed) - При присваивании переменных структурного типа, создается копия данных - Объявления полей структуры не могут иметь инициализаторов - Различная интерпретация this для структуры и класса - Структура не может содержать конструктор без параметров - Структура не может содержать деструктор - Для ссылочных типов значение по умолчанию - null - При конвертировании между ссылочным и размерным типами происходит упаковка и распаковка Главное помнить и понимать главные: 1 - следствием этого является то, что экземпляр класса создается в куче, а структуры, обычно (но не всегда) на стеке 3 - это должно быть очевидно 4- является следствием 6, т.к. код инициализации полей неявно вставляется во все конструкторы 6 является следствием оптимизации использования структур по скорости Особенности 8 для структур и 9 рассмотрим ниже более подробно, т.к. именно на них любят акцентировать внимание на собеседовании. Рассмотрим такой пример: 1.public struct MyStruct 2.{ 3. public int m1; 4. public string s1; 5.} 6.... 7.MyStruct ms1; 8.MyStruct ms2 = new MyStruct(); 9. 10.Console.WriteLine(ms1.m1); 11.Console.WriteLine(ms2.m1); 12.... 13. В чем отличие между объявлениями в строках 7 и 8? Некоторые считают, что в первом случае (строка 7) объект создается на стеке, а во втором (строка 8) происходит упаковка и объект создается в куче. На самом деле это не так. В обоих случаях объект создается на стеке. Разница в том, что в строке 7 будет создан неинициализированный объект, а в строке 8 инициализированный. Поля ms2 будут содержать значения по умолчанию (0 для m1 и null для s1), а поля ms1 неопределены. Поэтому в строке 10 возникнет ошибка компиляции. Теперь рассмотрим нюансы, связанные с упаковкой и распаковкой. Как известно, размерные типы могут наследоваться от интерфейсов (имплиментировать интерфейсы). Часто спрашивают, будет ли производиться упаковка при приведении размерного типа к интерфейсу. Правильный ответ - да, будет, т.к. интерфейс является ссылочным типом. Рассмотрим пример: 1.int i = 1; 2.Console.WriteLine(i.ToString()); 3.Console.WriteLine(((IFormattable)i).ToString()); 4.Console.WriteLine("{0}", i); 5. Размерный тип int имплиментирует интерфейс IFormattable, содержащий метод ToString(). Так как метод ToString() является частью класса int, а компилятор знает, что это размерный тип и, следовательно, виртуальный метод ToString() не может быть переопределен (т.к. структурный тип является запечатанным), компилятор вставляет непосредственный вызов метода в строку 2 и упаковки не происходит. В строке 3 происходит упаковка, т.к. i приводится к интерфейсу IFormattable. Теперь вы сами можете сказать, что происходит в строке 4: неявное приведение к интерфейсу IFormattable и вызов метода ToString(), что, также, приводит к упаковке. И еще один момент. Массивы являются ссылочными типами, но могут содержать размерные. Где же будет размещен, например, массив целых чисел? В куче, причем целые числа будут неупакованными. Значением this. Для класса: class Indirect { //... public void Method(Indirect that) { RefParameter(ref this); // compile-time error OutParameter(out this); // compile-time error this = that; // compile-time error } //... } Для структуры: struct Direct { //... public void Reassign(Direct that) { RefParameter(ref this); // compiles ok OutParameter(out this); // compiles ok this = that; // compiles ok } //... } Структура не может быть null, и вот это не пройдет: if (s == null) ... // compile-time error, where s - struct Структуру не можно использовать с оператором as Direct no = s as Direct; // compile-time error, where Direct - struct Структуру не можно использовать с оператором lock lock(s) { ... } // compile-time error, where s - struct Не может иметь полей типа volatile (Ключевое слово volatile указывает, что поле может быть изменено несколькими потоками, выполняющимися одновременно.) private volatile Direct field; // compile-time error, where Direct - struct Только структру могу работать с указателями, примеры (Ключевое слово unsafe обозначает небезопасный контекст, необходимый для работы с указателями.) Direct variable = new Direct(); unsafe { Direct * ptr = &variable; // compiles ok //... } но с классом Indirect variable = new Indirect(); unsafe { fixed(Indirect * ptr = &variable) // compile-time error { //... } } а так же Direct * array = stackalloc Direct[42]; // compiles ok Только структуры могу использовать sizeof int size = sizeof(Direct); // compiles ok По разному работает сравнение Equals struct Direct { public Direct(int value) { field = value; } private int field; } class Indirect { public Indirect(int value) { field = value; } private int field; } class EqualsBehavior { static void Main() { Direct s1 = new Direct(42); Direct s2 = new Direct(42); Indirect c1 = new Indirect(42); Indirect c2 = new Indirect(42); bool structEquality = s1.Equals(s2); // true bool classIdentity = c1.Equals(c2); // false } } 7. Что такое виртуальная функция? Приведите пример. В каких случаях используются виртуальные функции? В случае переопределяемых функций компилятор умеет отличать один вызов от другого по типу их аргументов. Используя эту информацию, он "жестко" связывает коды программы с соответствующими функциями элементами. С другой стороны, бывает необходимо отличить один вызов от другого, при наличии аргументов одного типа на этапе выполнения, и обеспечить потомки класса разными версиями функций базового класса. Именно использование ключевого слова virtual приводит к отсрочке связывания, и вызову нужной функции на этапе выполнения. Виртуальная функция элемент - это функция, которая будучи описана в потомках, замещает собой соответствующую функцию элемент везде - даже в предке, если она вызывается для потомка. В отличии от раннего связывания с использованием переопределяемых функций элементов, виртуальные функции элементы должны иметь аргументы одного типа. Синтаксис определения виртуальных функций элементов очень прозрачный: добавьте слово virtual к первому определению функции элементу: virtual void Show(); virtual void Hide(); Только встроенные функции элементы могут быть объявлены как виртуальные. Как только функция объявлена виртуальной, она не может быть переопределена ни в каком наследуемом классе с однотипным перечнем аргументов, но с другим типом возвращаемого значения. Если вы переопределяете Show с тем же перечнем однотипных аргументов и таким же типом возвращаемого значения, то новая функция Show автоматически становится виртуальной, независимо от того, используется ключевое слово virtual или нет. В этом случае говорят, что новая виртуальная Show замещает Show в своем базовом классе. Вы можете свободно переопределять Show с другим перечнем разнотипных аргументов (изменяя при этом тип возвращаемого значения или нет), но виртуальный механизм не задействуется для такой версии Show. акая именно функция элемент Show будет вызвана - зависит только от класса объекта, для которого вызывается Show, даже если вызов производится через указатель на базовый класс. Например, Circle ACircle Point* APoint_рointer = &ACircle; // указатель на Circle, // которому присваивается // значение указателя на // базовый класс, Point APoint_рointer->Show(); // вызывает Circle::Show! Так как вызов невиртуальной функции-элемента выполняется несколько быстрее, чем виртуального, то в общем случае рекомендуется, когда не встает вопрос о расширяемости, но существенное значение имеет производительность, пользоваться обычными функциями элементами. В противном случае, нужно пользоваться виртуальными. Литература 1. Баженова И. Ю. С++ && Visual Studio NET. Самоучитель программиста. - М.: КУДИЦ-ОБРАЗ, 2003. - 448с. 2. Гилберт Стивен, Макартни Билл. Самоучитель Visual C++ 6 в примерах. - К.: ООО "ТИД ДС", 2003. - 496с. 3. Касаткин А.И., Вальвачев А.Н. Профессиональное программирование на языке Си: От Турбо Си к С++. - Минск: Вышэйшая школа, 1992. - 240с. 4. Николенко Д. В. Самоучитель по Visual C++. - СПб : Наука и техника, 2001. -368 с. 5. Павловская Т.А. С/С++. Программирование на языке высокого уровня. - СПб.: Питер, 2003. - 461с. 6. Паппас К., Мюррей У. Программирование на С и С++. - К.: BHV, 2000. - 320c. 7. Савич У. С++ во всей полноте. - К.: BHV; СПб: Питер, 2005. 784 с. 8. Холзнер С. Visual C++: Учебный курс. СПб: Питер, 2000. - 576с. 9. Уэйт М., Прата С., Мартин Д. Язык Си. Руководство для начинающих. - М.: Мир, 1988. -512с. 10. Шиманович Е.Л. C/C++ в примерах и задачах. - Минск: Новое знание, 2004, - 528с. 11. Шмидский Я. К. Программирование на языке С/С++. Самоучитель. -М.: Вильямс, 2004. -352с.
|