Главная / Программирование /
Язык программирования C++ / Тест 7
Упражнение 1:
Номер 1
Для чего нужны классы?
Ответ:
(1) для определения новых типов в программе
(2) для упрощения работы со сложными структурами данных
(3) для упрощения работы с константами
(4) для соединения данных и операций над ними
Номер 2
В каком случае описание класса верно?
1. class A
{
public:
int x;
int summ(int a){return x+a;}
};
2. class my_cl
{
public:
int f;
int summ(int a){return x+a;}
};
3. class B
{
public:
int F;
void Ch_F(int x)
{
F=x;
return F;
}
};
Ответ:
(1) в первом
(2) во втором
(3) в третьем
Упражнение 2:
Номер 1
Какая из записей соответствует обращению к атрибутуm_argклассаACв определении метода этого же класса?
Ответ:
(1) this.m_arg.AC
(2) m_arg
(3) AC.this.m_arg
Номер 2
Если имеется код
class A { public: int a; };
A obj;
как обратиться к переменной a?
Ответ:
(1) obj.a
(2) obj-a
(3) obj::a
Номер 3
Если имеется код
class A { public: int a, b, c; };
A obj;
как обратиться к члену класса c?
Ответ:
(1) obj.c
(2) obj->c
(3) c.obj
(4) obj.public.c
Упражнение 3:
Номер 1
Какой из приведенных ниже прототипов операции сложения для класса
class A {int x; double y; ... }
является наиболее правильным?
Ответ:
(1) void operator+ (const A& a) const
(2) const A& operator+(A a)
(3) A operator+(const A a) const
(4) const A& operator(const A& a)
Номер 2
Какой будет результат выполнения следующего кода?
class A {
public:
int inc(int x) { return ++x; };
int inc(short x) { return x + 2; };
};
A obj; int y = 5;
cout << obj.inc(y);
Ответ:
(1) 6
(2) 7
(3) 8
Номер 3
Какой будет результат выполнения следующего кода?
class A {
public:
int y;
int inc(int x) { return ++y; };
int inc(short x) { return x + y; };
};
A obj; int y = 5; obj.y = 6;
cout << obj.inc(y);
Ответ:
(1) 6
(2) 7
(3) 11
(4) ошибка компиляции
Упражнение 4:
Номер 1
Укажите в какой строке кода произойдет ошибка компиляции?
1: class Channel
2: { public:
3: void SetNumber (int n) { number = n;};
4: int GetNumber() const { return number;};
5: int number;};
6: int main()
7: { Channel ch;
8: ch.number = 9;
9: ch.SetNumber(10);
10: Channel ch2(2);
11: return 1;
12: }
Ответ:
(1) в пятой
(2) в восьмой
(3) в десятой
Номер 2
Укажите в каких строках кода произойдет ошибка компиляции?
1: class A
2: { public:
3: int x;
4: int GetX() { return x; };};
5: int main()
6: { A b;
7: b.SetX(2);
8: b.GetX();
9: A b1(2);
10:}
Ответ:
(1) в восьмой
(2) в четвертой
(3) в седьмой
(4) в девятой
Номер 3
В каких выражениях правильно определен метод класса Ping?
class Ping
{ public: float f, d;
int a;
void MathFunc(double D);
};
Ответ:
(1) Ping::MathFunc(double D) {f = D;}
(2) void Ping::MathFunc(double D) {f = D;}
(3) void Ping::MathFunc(double D) {this->f = D;}
Упражнение 5:
Номер 1
Верен ли код
Team::Player p;
p.GoGoGo(); для класса Team, описанного следующим образом:
class Team
{
public:
int score;
class Player
{
public:
void GoGoGo(){ printf("Player is going to a goal.n"); }
};
void SetScore(int x){ score = x;}
void ShowScore(){printf("%dn",score);}
};
Ответ:
(1) да, верен
(2) нет, неверен
(3) верен, если сформировать контекстные указатели
(4) верен, если увеличить его приоритет
Номер 2
При определении метода запись this-> говорит о том, что:
Ответ:
(1) атрибут принадлежит объекту, получившему сообщение
(2) атрибут принадлежит классу, определенному в заголовочном файле
(3) атрибут не принадлежит какому-либо классу
Номер 3
Имеется класс:
class Team
{
public:
int score;
class Player
{
public:
void GoGoGo(){ printf("Player is going to a goal.n"); }
};
void SetScore(int x){ score = x;}
void ShowScore(){printf("%dn",score);}
};
Выберите из нижеприведенных записей корректные коды для этого класса:
1. Team::Player p;
p.GoGoGo();
2. Team t;
t.Player p;
p.GoGoGo();
Ответ:
(1) первый
(2) второй
(3) оба верны
(4) оба ошибочны
Упражнение 6:
Номер 2
Укажите правильный доступ к членам класса:
class my
{ public: double Z;
int f(int c, int d) {return c+d;}
char s;
} T1, T2;
Ответ:
(1) T1->Z = 23.1;
(2) T2.f(4,1);
(3) my.T2->s = 'L';
Номер 3
Укажите какому классу принадлежит атрибут Z1
class t
{ public: double sum::Z1;} C;
class sum
{ public: double t::Z1;} D;
Ответ:
(1) классу t
(2) обоим классам
(3) запись неверна
Упражнение 7:
Номер 1
Какой будет результат следующей программы?
class t
{ public: int sum;
float f(int a)
{
sum++;
return sum * a;
}
} cl;
int main()
{
cl.sum = 10;
cout << cl.sum << " " << cl.f(4);
}
Ответ:
(1) 11 44
(2) 10 44
(3) 10 240
Номер 2
Что будет выведено в стандартный поток вывода в результате исполнения следущей программы?
class Add
{ public: short S1;
int f(int x)
{ return S1 + ++x;}
int A(short a, short b);
} K1;
int Add::A(short a, short b)
{
this->S1 += a*b;
return this->S1;
};
int main()
{
K1.S1 = 2;
K1.f(2);
K1.A(0, 1);
cout << K1.S1;
return 0;
}
Ответ:
(1) 0
(2) 10
(3) 2
(4) 4
Номер 3
Какое второе число будет выведено в результате выполнения следующего кода?
class t
{ public: int sum;
float f(int a, short m)
{
sum++;
return sum * a - m;
}
} v;
int main()
{
v.sum = 5;
cout << v.sum << " " << v.f(5, 2);
}
Ответ:
(1) 27
(2) 28
(3) 29
(4) 5
(5) 6
Упражнение 8:
Номер 1
Какие операторы не могут быть переопределены пользователем:
Ответ:
(1) %
(2) ::
(3) .
Номер 2
Вызовет ли следующее объявление ошибку компиляции
class A{
public: void sum(double s1,double s2);
int sum (int s1,int s2);
};
Ответ:
(1) да
(2) нет
Номер 3
Вызовет ли следующее объявление ошибку компиляции
class A{
public: double sum(int double,double s2);
int sum (int s1,int s2);
}
Ответ:
(1) да
(2) нет
Язык программирования C++
Вызовет ли следующее объявление ошибку компиляции
class A{ public: double sum(int double,double s2); int sum (int s1,int s2); }
(Отметьте один правильный вариант ответа.)
Варианты ответа
Похожие вопросы
Вызовет ли следующее объявление ошибку компиляции
class A{ public: void sum(double s1,double s2); int sum (int s1,int s2); };
Укажите какому классу принадлежит атрибут Z1
class t { public: double sum::Z1;} C; class sum { public: double t::Z1;} D;
Укажите правильное объявление шаблона функции, если в программе производится вызов double х = zero<double>();
Если в программе уже имеется функция с прототипом int func(int k, double f), то какое из следующих объявлений не вызовет ошибки компиляции?
Какой результат будет у следующего выражения?
int m = 1, n=2; double A = (double)m/n; cout << A;
Какой результат будет у следующего выражения?
int m = 10, n = 4; double A = static_cast <double> (m)/n; cout << A;
Если имеется класс с двумя атрибутами
class Complex { double real; double img; . . . };
какой у него должен быть деструктор?
Если объявлен тип
struct Value {double tt; //Размер переменной типа double 64 битаunion number { short sx; // Размер переменной типа short 16 битов long lx; // Размер переменной типа long 32 бита double dx; // Размер переменной типа double 64 бита } val;};
сколько байтов занимает один объект такого типа?
Вызовет ли данный код ошибку компиляции?
class Rectangle{ public: int a,b; int sum(); int square(); ~rect(); };
Укажите правильный доступ к членам класса:
class my { public: double Z; int f(int c, int d) {return c+d;} char s; } T1, T2;
Правильные ответы выделены зелёным цветом.
Все ответы: В систематизированном виде излагаются основные понятия и описываются возможности языка C++. При этом основное внимание уделяется объяснению того, как теми или иными возможностями пользоваться.
Какие основные области применения языка Си++?
(1) системное программирование
(2) прикладное программирование
(3) программирование дизайна сайтов
Какие виды наследования бывают (выберите наиболее полный ответ)?
(1) внешнее,внутреннее,защищающее
(2) общее,внешнее,внутреннее,защищающее
(3) внешнее,внутреннее,защищенное
Какими по умолчанию объявляются методы класса?
(1) private
(2) public
(3) protected
(4) по умолчанию не объявляются
Какие бывают конструкторы?
(1) по умолчанию
(2) с параметрами
(3) инициализирующий
(4) копирующий
Выберите наиболее правильный вариант объявления оператора присваивания в классе A:
(1) A& operator=(const A& a);
(2) const A& operator=(const A& a);
(3) A& operator=(A a);
(4) A& operator=(const A a);
В чем заключается суть компоновки программы?
(1) в переводе текстового файла в объектный модуль
(2) в подготовке программы к выполнению
(3) в объединении нескольких фрагментов программы в один
У какой переменой в данном коде самое короткое «время жизни»?
char foo(char my_ch)
{
char ch= my_ch;
static int flag = 1;
if (flag){
char p;
p=ch;
ch=ch+1;
}
…..
return ch;
}
Какие ключевые слова используются для создания и обработки исключительных ситуаций?
(1) try
(2) delete
(3) catch
(4) return
(5) throw
Что понимается под потоком в языке C++
(1) обмен данными между программами
(2) обмен данными между компилятором и функцией main
(3) механизм ввода-вывода
Отметьте все утверждения, которые считаете верными:
(1) нельзя с помощью шаблона создать функцию с таким же именем, как у явно определенной функции
(2) цель введения шаблонов – создание функций, которые могут обрабатывать разнотипные данные
(3) в качестве описания шаблона функции используется прототип шаблона: template <список _параметров _шаблона >
Укажите неправильный идентификатор:
(1) AB_D1
(2) 10xd
(3) z1d8_14f3
Отметьте фрагменты кода, которые можно назвать выражениями:
(1) f + r*12 – 14
(2) int z;
(3) x = y = 13
Укажите правильное объявление?
(1) int 5;
(2) float fl,int i
(3) float F3v7G8t9F; int iCr3;
Прототип функции задает
(1) тип функции, включая количество и тип аргументов и тип результата
(2) возможность выполнения этой функции из программ на других языках программирования
(3) имя функции и минимальное количество параметров
Какие из перечисленных типов являются встроенными типами языка С++?
(1) float
(2) real
(3) integer
(4) bool
Для чего нужны классы?
(1) для определения новых типов в программе
(2) для упрощения работы со сложными структурами данных
(3) для упрощения работы с константами
(4) для соединения данных и операций над ними
Если в массиве A 132 элемента, каким будет правильное обращение к последнему элементу массива?
(1) A[132]
(2) A[131]
(3) A[133]
(4) A[-1]
Что из себя представляет динамическое выделение памяти?
(1) память под объект (переменную) выделяется каждый раз при обращении к переменной
(2) память под объект (переменную) может выделяться не сразу, а в процессе работы программы, освобождение памяти производится автоматически после завершения программы
(3) память под объект (переменную) может выделяться не сразу, а в процессе работы программы, освобождение памяти производится вручную
Программа на языке Си++ начинает выполняться с:
(1) первой функции в программе
(2) функции main
(3) той функции, которая указана как стартовая при компиляции программы
Если записано
class A { public: void f() { cout << 1; } };
class B : public A
{ public: void f() { cout << 2; } };
то что будет напечатано в результате выполнения кода?
B b; A& a=b; a.f();
(1) 2
(2) 2 1
(3) 1 2
(4) 1
(5) ошибка
Что целесообразно определять в public разделе класса?
(1) все,чтобы уберечь себя от ошибок
(2) все,что относится к интерфейсу класса
(3) все невиртуальные методы
Каким может быть аргумент деструктора?
(1) адрес объекта
(2) указатель this
(3) аргумента не может быть
(4) уничтожаемый объект
Класс B наследован от класса A. Отметьте верное для класса B.
(1) объект класса B может использоваться как объект базового класса
(2) класс B должен быть определен с ключевым словом derived
(3) класс B может непосредственно обращаться к внутренним атрибутам базового класса
Если в функции main() выполняется
…
int a=9; // в глобальном пространстве
void f() { int a; a = 4; }
…
cout << a;
то что будет выведено?
(1) 9
(2) 0
(3) 4
(4) другой
Возможно ли использовать механизм исключительных ситуаций в деструкторах
(1) можно, но обрабатывать их следует внутри деструктора
(2) да, никаких проблем возникнуть не может
(3) нет, компилятор выдаст ошибку
(4) да, но результат будет непредсказуем
Имеется функция шаблон
template <class T>
T func1(T a,T b)
{
if(a)
a=a%b;
return a;
}
Верен ли код
float a=5,b=6,c;
c=func1(a,b);
(1) да, все верно
(2) нет, ошибка связана с типом операндов в операторе if
(3) нет, ошибка связана с типом операндов в операторе %
Какой длины может быть идентификатор
(1) не более 64 символов
(2) не более 128 символов
(3) в самом языке по длине нет ограничений
Чему равен результат вычисления выражения
x + 3 * b + x
при x = 12 и b = 8 ?
Если int n=3, какой будет результат ?
switch(n) {
case 2: cout << «ааа»; break;
case 3: cout << «ббб»; break;
default: cout << «ввв»; break; }
(1) ошибка компилятора
(2) ааа
(3) ббб
(4) ввв
(5) неопределенное поведение
Отметьте, какому определению функции может соответствовать вызов func(5.98):
(1) int func(double x = 0, double y);
(2) void func(double x);
(3) double func(double x, int y = 12, int z = 5);
(4) void func(float arg1, float arg2 = 0);
(5) float func(float g, float f);
Какая из записей соответствует обращению к атрибуту m_arg класса AC в определении метода этого же класса?
(1) this.m_arg.AC
(2) m_arg
(3) AC.this.m_arg
Что описывает данный программный код?
struct {
char fio[30];
int date, code;
double salary;
}staff[100], *ps;
(1) определение массива структур и указателя на структуру
(2) копирование строки salary в строку staff
(3) создание указателя на строку date
(4) создание итератора с указателем на строку date
Компилятор языка Си++:
(1) переводит текст программы в машинные инструкции
(2) выполняет программу
(3) форматирует текст программы так, чтобы его было удобно читать
Если имеется абстрактный класс А и производный от этого класса класс А1
то какая из записей заведомо неверна?
(1) A a;
(2) A1 a1;A& a=a1;
(3) A* a= new A;
Можно ли перегружать оператор разрешения области видимости -«::»
Что произойдет, если определение класса будет находиться в файле в двух местах?
(1) будет использоваться второе определение
(2) второе определение будет проигнорировано
(3) зависит от других факторов
(4) ошибка компиляции
(5) ошибка произойдет при сборке программы из нескольких файлов
Отметьте все верные утверждения о статических атрибутах класса:
(1) по умолчанию помещаются в public часть класса
(2) существуют в единственном экземпляре, независимо от количества объектов
(3) инициализируются при создании первого объекта
(4) не могут изменяться
(5) инициализируются в начале выполнения программы
Если заданы классы
class A {… } A1;
class B : public A { … } B1;
class C : public A { … } C1;
то что будет выведено при выполнении оператора
throw (C1);
а обработка исключительной ситуации записана
catch (B& b) { cout << 1; }
catch (C& c) { cout << 2; }
catch (A& a) { cout << 3; }
catch (…) { cout << 4; }
(1) 1
(2) 2
(3) 3
(4) 4
(5) 3 4
(6) 2 3 4
Что будет выведено в результате
double x = 12.4;
cout << setw(5) << x << setw(3)
<< setfill(‘*’)<< «» << endl;
(1) "12.40***"
(2) " 12.4***"
(3) "12.4 * *"
(4) "12.40"
(5) ".124e2**"
Отметьте все утверждения, которые считаете верными:
(1) нельзя с помощью шаблона создать функцию с таким же именем, как у явно определенной функции
(2) цель введения шаблонов – создание функций, которые могут обрабатывать разнотипные данные
(3) в качестве описания шаблона функции используется прототип шаблона: template <список _параметров _шаблона >
Какое слово из списка не относится к зарезервированным словам Си++?
(1) try
(2) union
(3) cast
(4) volatile
Если функция вычисления суммы целых чисел от 1 до n имеет прототип int sum(int n), запишите определение функции, используя рекурсию:
(1)
{if (n == 1) return 1;
else return n + sum (n – 1);}
(2)
{if (n == 1) return 1;
else return sum(n);}
(3)
{if (n == 1) return 1;
else return sum(n) + (n – 1);}
(4)
{if (n == 1) return 1;
else return sum(n) + sum (n-1);}
Что произойдет после объявления в программе данного набора перечисляемых значений: enum{N=0, E=1, S=2, W=3};?
(1) программа будет работать с числовыми значениями N, Е, S и W
(2) программа будет работать с идентификаторами N, Е, S и W
(3) программа сформирует итераторы с указателями на N, Е, S и W
(4) программа будет игнорировать все указатели на N, Е, S и W
Какой из приведенных ниже прототипов операции сложения для класса
class A {int x; double y; … }
является наиболее правильным?
(1) void operator+ (const A& a) const
(2) const A& operator+(A a)
(3) A operator+(const A a) const
(4) const A& operator(const A& a)
Какой результат следующего выражения ?
int* a; int b; a = &b; b = 7; *a++; cout << b;
(1) 7
(2) 8
(3) не определено
(4) ошибка компиляции
Что будет напечатано в результате выполнения следующего кода?
int x=39, *p = &x;
cout << p << «__» << *p ;
(1) ошибка компиляции
(2) не определено
(3) адрес в памяти__39
(4) 39_адрес в памяти
Процесс компиляции программы
(1) переводит исходный текст в исполняемый файл
(2) приводит программы к единообразному внешнему виду
(3) для языка Си++ необязателен
Какая из записей является правильной записью абстрактного класса?
(1) abstract class A { virtual int f() = 0; };
(2) class A { virtual int f() = 0; };
(3) class A { virtual int f(); };
Какие из следующих объявлений метода func синтаксически правильны?
(1) void func(const Foo& a);
(2) void func(Foo& a) const;
(3) void func(const Foo& a) const;
Если в классе операция new переопределена как
void* operator new(size_t size, int a);
то какой вызов этой операции правильный?
(1) Foo* ptr = new (20) Foo;
(2) Foo* ptr = new Foo(20);
(3) Foo* ptr = new [20] Foo;
Нужно ли учитывать при перегрузке бинарных операций порядок следования операндов?
(1) необходимо учитывать
(2) необходимо определять
(3) необязательно учитывать
(4) необязательно определять
Объявление extern int f; означает:
(1) переменная определена в другом исходном файле
(2) переменная определена в стандартной библиотеке
(3) переменная может быть использована только в этом файле
Если в конструкторе класса
class A {
public:
A() { ptr = new char[size];
Init(); }
~A() { if (ptr) delete[] ptr; }
char* ptr; };
произойдет исключительная ситуация, будет ли потеряна память при откате по стеку?
(1) да, будет, во всех случаях
(2) будет, только если объект класса создавался с помощью new
(3) будет, если создавалась автоматическая переменная класса a
(4) нет, не будет
(5) зависит от конкретного компилятора
Результат работы программы:
#include <iostream.h>
int main()
{
int ic;
cout << «Введите любую десятичную цифру:»;
cin >> ic;
switch (ic)
{
case 0: case 1: cout << «один «;
case 2: case 3: cout << «три «;
case 4: case 5: cout << «пять «;
case 6: case 7: cout << «семь «;
case 8: case 9: cout << «девять «; break;
default: cout << «ERROR!!!»;
}
return 1;
}
(1) компилятор найдет ошибку
(2) если введена нечетная цифра, выводится ее название
(3) в любом случае выводится «ERROR!!!»
(4) выводятся названия всех нечетных цифр больше или равных введенной
Шаблон A и его специализации объявлены следующим образом:
template <class T> class A
{
public:
A(){ printf(«1 «);}
};
template <> class A<int>
{
public:
A(){ printf(«2 «);}
};
template <> class A<char*>
{
public:
A(){ printf(«3 «);}
};
Какой будет результат после выполнения кода
A<int> a;
A<char> a1;
A<long> a2;
(1) 2 3 1
(2) 2 1 1
(3) 1 1 1
(4) ошибка компиляции в строке "template <> class A<int>"
Выберите правильное объявление константы pi:
(1) const float pi = 3.14;
(2) float pi = (const) 3.14;
(3) const float pi 3.14;
Операция ++
(1) увеличивает значение переменной на единицу
(2) увеличивает значение переменной на два
(3) уменьшает значение переменной на единицу
(4) уменьшает значение переменной на два
(5) в языке Си++ не существует
Что означает запись for (;;);?
(1) бесконечный цикл
(2) цикл, который не выполняется ни разу
(3) ошибка компиляции
(4) аварийный выход из программы
Совокупность типов формальных параметров, их порядка и имени функции определяет:
(1) тип возвращаемого функцией значения
(2) сигнатуру (подпись) функции
(3) идентификатор функции
(4) последовательность описаний и определений функции
Укажите в какой строке кода произойдет ошибка компиляции?
1: class Channel
2: { public:
3: void SetNumber (int n) { number = n;};
4: int GetNumber() const { return number;};
5: int number;};
6: int main()
7: { Channel ch;
8: ch.number = 9;
9: ch.SetNumber(10);
10: Channel ch2(2);
11: return 1;
12: }
(1) в пятой
(2) в восьмой
(3) в десятой
Для получения адреса переменной используется операция
(1) *
(2) &
(3) ->
(4) .
(5) нет правильного ответа
Что выполняет операция «delete [] v;» в данном ниже коде:
class MyClass
{
int sz; // число элементов
int * v; // указатель на целые
public:
MyClass ( int );
~MyClass ();
int&operator [] ( int index ); // операция индексации
};
…
MyClass::~ MyClass()
{
delete [] v;
}
(1) удаляет первый элемент из массива «v»
(2) удаляет последний элемент из массива «v»
(3) удаляет весь массив «v», освобождая память
(4) удаляет указатель на массив «v» из памяти
В программе на языке Си++ обязательно имеется функция
(1) head
(2) start
(3) prime
(4) main
(5) finish
Какой будет результат выполнения следующего кода?
class A {
public:
int inc(int x) { return ++x; };
int inc(short x) { return x + 2; };
};
A obj; int y = 5;
cout << obj.inc(y);
Отметьте правильные объявления переменных
(1) const int s = 10; int a[s];
(2) int s = 10; const int a[s];
(3) int s = 10; int a[] = new int[s+s];
Конструктор класса — это метод, который вызывается при создании объекта для …(перечислить )
(1) выделения памяти под динамические атрибуты класса
(2) выделения памяти под статические атрибуты класса
(3) инициализации атрибутов объекта
(4) загрузки методов класса в память
Если определена операция умножения для двух объектов класса A и операция преобразования к int, что будет вызвано при
A a;
int x;
int y = a * x;
(1) операция умножения, а затем преобразование к целому
(2) преобразование к целому
(3) только операция умножения
Может ли статический метод класса быть объявлен как friend?
Отметьте, какие возможности языка Си++ помогают предупреждать ошибки:
(1) наличие встроенных типов данных
(2) контроль типов при компиляции
(3) возможность использовать указатели вместо массивов
(4) обязательность объявления функций до их использования
Какой класс используется для вывода данных во внутреннюю область памяти?
(1) iostream
(2) strstream
(3) cout
(4) strout
(5) fstream
Отметьте правильное определение константы:
(1) const int DOZEN = 12;
(2) const double;
(3) double COFF = (const)1.2e10;
Чему равно значение выражения !((1 || 0) && 0) ?
(1) 0
(2) 1
(3) ошибка компиляции
В каких выражениях произойдет зацикливание программы?
(1) for (int iCount = 0; iCount <= 4;);
(2) while (true);
(3) while (false);
Что вычисляет эта функция:
double func(double x, int n) {
if (n == 0) return 1;
if (x == 0) return 0;
if (n > 0) return x * func(x, n-1);
if (n < 0) return func(x, n+1) / x;
}
(1) дробную степень вещественного ненулевого числа
(2) целую степень любого числа
(3) факториал
(4) любую степень любого числа
Укажите в каких выражениях правильно определены целочисленные переменные?
(1) short x = 23;
(2) unsigned int a = 2, b = 3;
(3) const unsigned char c;
(4) unsigned short g = 0x2;
Верен ли код
Team::Player p;
p.GoGoGo();
для класса Team, описанного следующим образом:
class Team
{
public:
int score;
class Player
{
public:
void GoGoGo(){ printf(«Player is going to a goal.n»); }
};
void SetScore(int x){ score = x;}
void ShowScore(){printf(«%dn»,score);}
};
(1) да, верен
(2) нет, неверен
(3) верен, если сформировать контекстные указатели
(4) верен, если увеличить его приоритет
Укажите какой результат будет у следующего примера?
int array[10]; array[1] = 2; array[10] = 3;
cout << array[10];
(1) ошибка компиляции
(2) непредсказуемый результат из-за выхода за границы массива
(3) 3
(4) 0
Какой будет результат у данного выражения?
int *i = new int;
…
i = new int;
…
delete i;
(1) ошибка компиляции
(2) утечка памяти
(3) выделение памяти под переменную не произойдёт
Ключевое слово void обозначает что функция
(1) возвращает число с плавающей запятой
(2) возвращает целое число
(3) ничего не возвращает
(4) является главной
Укажите какое из выражений будет правильным?
class A
{ public : int a, b;
protected : int func (float d) {};
private : short i; } A1;
class B : public A
{ private : int k; } B1;
class C : public B
{ protected : int l, m; } C1;
(1) B1.k = 3;
(2) C1.a = C1.b;
(3) C1.a = C1.i;
(4) C1->a = C1->b;
Какая функция класса, не являясь его компонентом, имеет доступ к его защищенным и внутренним компонентам?
(1) дружественная
(2) шаблонная
(3) макрофункция
(4) статическая
В каких строках ошибка?
new long[];
new long[][2][4];
new long[3][][4];
(1) во всех строках ошибка – неизвестен размер
(2) в строках нет ошибки
(3) в первой строке ошибка – неизвестен размер
(4) в последней строке ошибка – неправильный синтаксис
(5) во всех строках ошибка – неправильный синтаксис
Какой тип преобразования типов используется в следующем выражении?
int a = 0; float f = 3.4; a += f;
(1) неявное преобразование типов
(2) явное преобразование типов
(3) пользовательское преобразование типов
С помошью какой директивы происходит подключение других модулей программы?
(1) #include
(2) #define
(3) #ifndef
Что является минимальной областью видимости имен?
(1) модуль
(2) блок
(3) функция
(4) класс
Что может быть аргументом оператора throw?
(1) целое число
(2) объект класса
(3) строка
(4) ноль
(5) условный оператор
(6) вызов деструктора объекта
(7) вызов оператора return
Что произойдет при выводе в файл, открытый с помощью
ofstream("filename", ios::out|ios::app|ios::trunc)
(1) вывод будет производиться в двоичном виде
(2) вывод будет производиться в конец файла
(3) можно читать из файла
(4) если файл существовал, его содержимое сотрется
(5) вывод будет производиться записями фиксированной длины
Отметьте правильный заголовок шаблона функции:
(1) template <class T> void Sum(T x1, T x2);
(2) class template <class T> Sum
(3) template <T> class
(4) template class <class T> Sum(T x1, T x2);
В каких выражениях используются бинарные арифметические операции?
(1) X + Y
(2) c % d + 2
(3) xx * Y
(4) xx++
Укажите каким будет результат вычисления k?
int func()
{
int k = 10;
for (int i = 0; i <= k; i++)
{
return 5;
k = i;
}
}
(1) 0
(2) 10
(3) 1
(4) бесконечный цикл
(5) 100
Какое из следующих утверждений об операторе return является верным?
(1) оператор return должен стоять последним в теле функции
(2) оператор return завершает выполнение функции
(3) в теле функции должен присутствовать только один оператор return
Какой результат у следующего выражения?
struct str
{
int a = 2;
float b = 10;
} m1, m2;
cout << m1.a * m2.b;
(1) 20
(2) 2
(3) 10
(4) ошибка компиляции
Что описывает данная строка программы: float mas=new int[3][2]?
(1) создание двумерного динамического массива размерности 3*2
(2) создание одномерного динамического массива из 3 элементов
(3) создание одномерного динамического массива из 2 элементов
(4) данная строка представляет собой ошибочную запись и работать не будет
Какие компоненты могут входить в интегрированную среду программирования
(1) текстовый редактор
(2) отладчик
(3) компилятор
Сколько производных классов можно получить из базового класса?
(1) неограниченное количество
(2) количество производных классов определяется количеством базовых классов
(3) определяется наличием абстрактного класса
С помощью механизма friend можно разрешить обращение к внутренним элементам класса:
(1) отдельной функции
(2) отдельному методу другого класса
(3) всем методам другого класса
При использовании копирующего конструктора:
(1) вновь созданный объект имеет те же атрибуты что и исходный
(2) вновь созданный объект зависит от копируемого
(3) вновь созданный объект не зависит от копируемого
Какой результат будет у следующего выражения?
int m = 1, n=2;
double A = (double)m/n;
cout << A;
(1) 0.5
(2) 0
(3) 1
(4) ошибка компиляции
Если в программе объявлен макрос #define CIRC(x) (3.14159 * (x) * (x)), то как будет подставлен этот макрос в тексте программы при следующем вызове:
S = CIRC(a + b);
(1) S = (3.14159 * a + b * a + b);
(2) S = (3.14159 * (a + b) * (a + b));
(3) S = (3.14159 * (a + b));
Что будет выведено в стандартный поток в результате выполнения программы
int main()
{ int x1 = 5;
x1 = x2 - 2;
int x2 = x1;
cout << x2;
return 0;
}
(1) -2
(2) 5
(3) 0
(4) ошибка компиляции
Что будет на экране после выполнения программы
#include <iostream.h>
short x = 4, i = 0;
void fun1()
{ if (i == 0) throw 2; }
int fun2()
{ --x; fun1(); x++; return x; }
int main()
{ try
{ fun2(); }
catch (int)
{ cout << "Exception "; }
cout << x << " " << i;
}
(1) Exception
(2) Exception 4 0
(3) Exception 3 0
(4) ошибка компиляции
Для чего предназначены манипуляторы потоков ввода-вывода?
(1) для управления состоянием потока
(2) для перенаправления потоков ввода-вывода
(3) для создания объектов классов istream и ostream
Какая строка данного кода производит специализацию шаблона?
1:template<class Т> class А{
2:int х;
3:};
4:template<class Т> class А<Т*> {
5:long х;
6:};
7:template<template<class U> class V> class C{
8:V<int> y;
9:V<int*> z;
10:};
11:C<A> c;
(1) последняя
(2) предпоследняя
(3) седьмая
(4) четвертая
Укажите правильные присваивания значений переменным и константам
(1) float Y = 12, int Y = 3;
(2) int iCode = 12123; int Viv_12 = iCode;
(3) const int Const = 2; int iConst = Const + 3;
Укажите в каком выражении используется операция с наивысшим приоритетом?
(1) x << 3
(2) c + D
(3) a2 >> 5
(4) k++
Какое определение функции является правильным?
(1)
int f(int b,int c)
{return;}
(2)
void f(int b,int c)
{return b+c;}
(3)
int f(int b,int c)
{return 0;}
Если функция вычисления суммы целых чисел от 1 до n имеет прототип int sum(int n), то как будет выглядеть запись определения функции с использованием рекурсии?
1.{if (n == 1) return 1;
else return n + sum (n - 1);}
2. {if (n == 1) return 1;
else return sum(n);}
(1) первый вариант
(2) второй вариант
(3) оба верны
(4) оба ошибочны
Представление и диапазоны значений вещественных чисел соответствуют стандарту:
(1) ASCII
(2) ANSI
(3) IEEE
(4) CP-1251
Какой будет результат следующей программы?
class t
{ public: int sum;
float f(int a)
{
sum++;
return sum * a;
}
} cl;
int main()
{
cl.sum = 10;
cout << cl.sum << " " << cl.f(4);
}
(1) 11 44
(2) 10 44
(3) 10 240
Для создания исполняемого файла в системе Unix необходимо
(1) только набрать текст в текстовый файл
(2) только запустить любой из имеющихся компиляторов: GNU C++, g++, c++, cc
(3) набрать текст в текстовый файл и запустить его на компиляцию любым из имеющихся компиляторов C++
Какой результат будет у следующего выражения?
class A
{ public : int a, b;
protected : int z;
private : short i; } A1;
class B : private A
{ private : int k;} B1;
int main()
{ B1.a = 10;
cout << B1.a;
}
(1) 10
(2) ошибка
(3) 0
Какой результат будет у следующего выражения?
class A
{ friend int Freund();
friend class B;
public : int x, y;
private: short i;
} A1;
class B
{ public : void func_B(); }B1;
int Freund()
{ A1.x = 1; A1.y = 2; A1.i += 3;
return A1.x + A1.y + A1.i;
}
void B::func_B()
{ A1.i = 2; }
int main()
{ B1.func_B(); cout << Freund(); }
(1) 6
(2) 8
(3) 12
Если в производном классе переопределена операция new то:
(1) все объекты класса и все производные от этого класса будут использовать эту операцию
(2) производные от этого класса могут использовать глобальную операцию применив операцию ::new
(3) базовый класс также будет использовать переопределенную операцию
Если в арифметическом выражении участвуют целый и вещественный операнды, то:
(1) целый тип приводится к вещественному
(2) вещественный тип приводится к целому
(3) ошибка компиляции
Отметьте истинные высказывания в отношении использования макросов и функций:
(1) в большинстве случаев функции позволяют сокращать объем выполняемого файла
(2) в большинстве случаев макросы позволяют сокращать время выполнения
(3) недостатком макросов является отсутствие встроенного контроля согласования типов аргументов и формальных параметров
int a;
void A() { static int a = 4; };
};
Какой будет результат выполнения cout << a::A::a; в функции main?
(1) 4
(2) 0
(3) ошибка компиляции
(4) ошибка выполнения
Какой результат будет у следующего выражения?
int main()
{ try
{
try
{
try { throw 1; }
catch (int) { cout << "Exception 1"; }
}
catch (int) { cout << "Exception 2"; }
}
catch (int){ cout << "Exception 3"; }
return 0;
}
(1) Exception 1Exception 2Exception 3
(2) Exception 1Exception 2
(3) Exception 1
(4) Exception 2
(5) Exception 3
Что будет на экране после выполнения программы?
#include <iostream.h>
#include <fstream.h>
#include <string.h>
int main()
{
int i = 1, j = 25; double a = 25e6; char s[40];
strcpy(s, "Test");
ofstream outfile("c:\test.dat");
if (!outfile)
{ cout << "Ошибка создания файла";
return 1; }
outfile << i << ' ' << j << ' ' << a << ' ' << s << endl;
outfile.close();
}
(1) будет создан текстовый файл "C:test.dat" с содержимым "1 25 2.5e+07 Test"
(2) будет выведено сообщение "Ошибка создания файла"
(3) будет создан текстовый файл "C:Test" с содержимым "1 25 2.5e+07 Test"
(4) будет создан двоичный файл
(5) в ходе выполнения возникнет исключение
Какой будет результат следующего выражения?
template <class T> T sum(T *a, T *b)
{ T f = 5;
return (*a + *b) - f;
}
int main()
{ int i = 10, j = 20; double x = 5.1, y = 2.2;
cout << sum(&i, &j) << " " << sum(&x, &y);
}
(1) 25 2.3
(2) 20 2.3
(3) 25 22
(4) ошибка компиляции
Укажите в каких выражениях используются ключевые слова?
(1) sdf = 2; int r = 24;
(2) TStringList *S = new TStringList;
(3) x = 3; x = x + 4;
(4) void function()
Чему будет равен результат вычисления:
int i, k = 2, m = 10;
i = (m)/(m/k - 5);
(1) 10
(2) 0
(3) 5
(4) ошибка во время исполнения
Что будет выведено на экран в результате выполнения кода?
int a=3;
if (a>1)
cout << "1";
else
if(a>2)
cout << "2";
else
if(a>3)
cout << "3";
(1) 123
(2) 12
(3) 1
Имеется функция
int sum(int a,int a1=2,int a2,int a3,int a4)
{return a+a1+a2+a3+a4;}
что будет выведено на экран после выполнения кода cout<<sum(9,8,7,6);
(1) ничего,будет ошибка компиляции
(2) 30
(3) 32
Если имеется объявление char ch1='a',ch2='b',ch3='c';
допустима ли запись ch1=ch2+ch3;
(1) нет
(2) да
Какие операторы не могут быть переопределены пользователем:
(1) %
(2) ::
(3) .
Что будет выведено на экран в результате выполнения данного кода?
int main()
{
......
float a1 = 7, a2 = 8;
{
float p=4;
float p1=5;
p1+=5;
}
if (a2) a1+=a2+p1++;
cout << a1;
.....
}
(1) 25
(2) 24
(3) ничего из-за ошибки компиляции
Существует ли в С++ готовый набор шаблонов:
(1) нет
(2) да,существует специальная библиотека STL
(3) зависит от версии компилятора
Какой статус международного стандарта языка Си++?
(1) принят только в США и ждет одобрения международной организации
(2) принят ISO и тем самым автоматически принят во всех странах
(3) принят проект стандарта, дорабатывается
Что будет в результате выполнения следующей программы?
class Base{
…
public: void f();
private: int *baseID;
};
class Derived : public Base{
…
public: void foo();
private: int derivedID;
};
Derived my1;
int x;
my1.baseID=&x;
(1) присвоение будет выполнено
(2) результат непредсказуем
(3) ошибка компиляции
(4) сообщение об ошибке внутренних адресов
Какими по умолчанию объявляются элементы структуры?
(1) private
(2) public
(3) protected
(4) по умолчанию не объявляются
Имеется три объекта класса А:
A a1,a2,a3 в каком выражении возможен неявный вызов конструктора копирования
(1) a3=a1+a2;
(2) a2.sum(a3);
(3) a2.sum(&a3);
Что является результатом компоновки программы?
(1) заголовочный файл
(2) исполняемый файл или библиотека
(3) набор заголовочных файлов с определением в них всех используемых функций
У какой переменой в данном коде самое длинное "время жизни"?
char foo(char my_ch)
{
char ch= my_ch;
static int flag = 1;
if (flag){
char p;
p=ch;
ch=ch+1;
}
.....
return ch;
}
(1) p
(2) flag
(3) ch
Функция вычисляет произведение двух чисел. Исходные данные вводятся с клавиатуры. Какие проверки целесообразно ввести в программе?
(1) проверка исходных данных на равенство нулю
(2) проверка, что исходные данные являются числами и эти числа больше нуля
(3) проверка, что исходные данные являются числами
(4) проверки не нужны, все возможные ошибки отловит компилятор
Какое из представленных выражений выводит на экран
0000
0 0
0000
Замечание:при написании собственных программ с использованием форматированного ввода/вывода подключите файл iomanip.h
(1)
cout<<setw(4)<<setfill('0')<<""<<endl;
cout<<setw(1)<<setfill('0')<<"";
cout<<" "<<setw(1)<<setfill('0')<<""<<endl;
cout<<setw(4)<<setfill('0')<<""<<endl;
(2)
cout<<setw(4)<<setfill('0')<<""<<endl;
cout<<setw(1)<<setfill('0')<<""<<" "<<setw(1)<<setfill('0')<<""<<endl;
cout<<setw(4)<<setfill('0')<<""<<endl;
(3)
cout<<setw(4)<<setfill('0')<<""<<endl<<setw(1)<<setfill('0')<<""
<<" "<<setw(1)<<setfill('0')<<""<<endl<<setw(4)<<setfill('0')<<""<<endl;
Какой правильный заголовок шаблона
(1) template <class t1, class t2>
(2) template <class t1,t2>
(3) template <class t, class t>
(4) template <class t,t>
Укажите правильный идентификатор для имени переменной:
(1) FA_Ф12
(2) _ri18
(3) int
(4) 2a
Операции в выражениях могут быть
(1) только унарными или бинарными
(2) только бинарными
(3) унарными,бинарными или тернарными
При выполнении фрагмента кода
int x = 3, y = 2, z = 1;
if(x >= y)
if(y <= z)
std::cout << "Вариант 1";
else
if(x >= z)
std::cout << "Вариант 2";
else
std::cout<<"Вариант 3";
будет напечатано:
(1) Вариант 1
(2) Вариант 2
(3) Вариант 3
(4) ничего не будет напечатано
(5) программа не откомпилируется
Сколько функций может быть в программе С++?
(1) ни одной
(2) минимум одна
(3) не больше 100
Какие из следующих выражений являются константами типа double?
(1) 3.0 + 2
(2) 5.00E+06
(3) 0.8
(4) 12lU
В каком случае описание класса верно?
1. class A
{
public:
int x;
int summ(int a){return x+a;}
};
2. class my_cl
{
public:
int f;
int summ(int a){return x+a;}
};
3. class B
{
public:
int F;
void Ch_F(int x)
{
F=x;
return F;
}
};
(1) в первом
(2) во втором
(3) в третьем
Если имеется объявление int a[15], какой фрагмент кода выводит все элементы массива?
(1) for (int k = 0; k <= 15; k++) cout << a[k];
(2) for (int k = 1; k < 16; k++) cout << a[k];
(3) for (int k = 0; k < 15; k++) cout << a[k];
Какое из приведенных выражений верно?
(1) int a; a = new int[20];
(2) int a; a = new int(20);
(3) int *a; a = new int[20];
(4) int *a; a = new 20;
(5) int *a; a = new sizeof(int*20);
(6) int a; a = new sizeof(int*20);
Что такое cout?
(1) объект типа iostream (std::ostream)
(2) класс, который выводит данные на терминал
(3) переменная, которую программист должен создать для вывода данных
Будет ли вызываться конструктор, если в программе встретится следующaя конструкция:
monstr Super(200, 300), Vasia(50), Z;
monstr X = monstr(1000);
monstr Y = 500;
(1) нет, не будет
(2) да, будет
(3) данная конструкция содержит синтаксическую ошибку
(4) данный код вызовет ошибку компиляции
Для переопределенного оператора верно:
(1) переопределенный оператор выполняется с тем же приоритетом, что и исходный оператор
(2) у переопределенного оператора не может быть аргументов по умолчанию
(3) переопределенный оператор может быть унарным оператором
(4) если переопределенный оператор не является методом класса, то у него должен быть, по крайней мере, один аргумент типа класса, определенного пользователем
Сколько вызовов системных функций всегда будет в программе, независимо от ее реализации?
(1) всегда 2
(2) по крайней мере один
(3) не менее двух
Если имеется программа
int a = 7; //в глобальном пространстве
void f() { static int a; a += 5; }
и в функции main() выполняется
f(); cout << a;
то какой будет результат?
(1) 5
(2) 7
(3) 12
(4) другой
На каком этапе обнаруживаются ошибки в алгоритме программы?
(1) на этапе компиляции
(2) на этапе выполнения
(3) они могут не проявиться никогда, все зависит от входных данных
Если имеется код int x; cin >> x; и вводится "1.2", то что будет в переменной x?
(1) 1
(2) 2
(3) 1.2
(4) другое
(5) произойдет ошибка
Если есть два объявления int qwerty; int QWERTY; какое из
утверждений верно
(1) такие имена переменных недопустимы
(2) объявления правильные
(3) такие объявления недопустимы, так как мы пытаемся создать две одинаковые переменные
Чему равен результат вычисления выражения
x + 3 * b / 2
при x = 12 и b = 8 ?
(1) 24
(2) 60
(3) 120
Если в программе уже имеется функция с прототипом int func(int k, double f), то какое из следующих объявлений не вызовет ошибки компиляции?
(1) double func(int m, double g)
(2) int func(double x, int y)
(3) void func(int m, double g = 3.14)
Что произойдет после следующего примера?
cout << "nnn" << "Hello World" << "a";
(1) прозвенит звонок и выйдет сообщение "Hello World"
(2) в четвертой строке выйдет сообщение "Hello World" и прозвенит звонок
(3) выйдет сообщение "Hello Worlda"
Если имеется код
class A { public: int a; };
A obj;
как обратиться к переменной a?
(1) obj.a
(2) obj-a
(3) obj::a
Определите размер структуры
struct {
char fio[30];
unsigned char date:4;
unsigned char code:4;
};
(1) 9 байт
(2) 38 байт
(3) 31 байт
(4) 39 байт
Какой будет результат ?
int f(int& x) {
static int a = 0; if (!a) a = ++x; return a;}
int y = 6; f(y); cout << f(y) << y;
(1) 77
(2) 78
(3) 70
(4) 79
После компиляции программы
(1) ее можно выполнять многократно без перекомпиляции
(2) перед каждым последующим запуском ее нужно перекомпилировать
(3) ее можно выполнять только с одним набором исходных данных
Если имеется класс с двумя атрибутами
class Complex {
double real;
double img;
. . .
};
какой у него должен быть деструктор?
(1) виртуальный деструктор
(2) деструктор, вызывающий деструкторы атрибутов
(3) деструктор не обязателен
Какой тип будет у следующего выражения ?
void* + reinterpret_cast<int*>1000
(1) void*
(2) int*
(3) int
(4) ошибка компиляции
(5) ошибка выполнения
Что произойдет, если определение функции будет находиться в файле в двух местах?
(1) будет использоваться второе определение
(2) второе определение будет проигнорировано
(3) зависит от других факторов
(4) ошибка компиляции
(5) ошибка произойдет при сборке программы из нескольких файлов
Отметьте все верные утверждения о статических методах класса:
(1) не могут иметь аргументов по умолчанию
(2) не существуют до создания первого объекта класса
(3) не могут объявляться со словом const в конце объявления
Если заданы классы
class A {... } A1;
class B : public A { ... } B1;
class C : public A { ... } C1;
то что будет выведено при выполнении оператора
throw (A1);
а обработка исключительной ситуации записана
catch (B& b) { cout << 1; }
catch (C& c) { cout << 2; }
catch (A& a) { cout << 3; }
catch (...) { cout << 4; }
(1) 1
(2) 2
(3) 3
(4) 4
(5) 3 4
(6) 2 3 4
Какой из стандартных классов используется для вывода строк на терминал:
(1) strstream
(2) ostream
(3) ofstream
(4) istream
(5) ifstream
Какой правильный заголовок шаблона
(1) template <class t1, class t2>
(2) template <class t1,t2>
(3) template <class t, class t>
(4) template <class t,t>
Какое из приведенных имен является недопустимым в Си++?
(1) x03488erJJJ___
(2) xb___@
(3) r13
(4) OOP
Каково будет значение переменной k после выполнения следующего оператора
k = ++k;
если до его выполнения k равнялось 6?
(1) 6
(2) 7
(3) 8
Если функция вычисления факториала n имеет прототип int fact(int n), отметьте код, использующий рекурсию и правильно вычисляющий значение факториала:
(1)
{if (n == 0 || n==1) return 1;
else return n * fact (n -1);}
(2)
{if (n == 0 || n==1) return 1;
else return fact(n);}
(3)
{if (n == 0 || n==1) return 1;
else return fact(n) * fact(n-1);}
(4)
{if (n == 0 || n==1) return 1;
else return fact(n)*(n-1);}
Какой из наборов перечисляемых значений записан правильно?
(1) enum { a, b, 3, 4 };
(2) enum { a, b = 3, c = 4, 3 };
(3) enum {a, b = 3, c, d };
Какой будет результат выполнения следующего кода?
class A {
public:
int inc(int x) { return ++x; };
int inc(short x) { return x + 2; };
};
A obj; int y = 5;
cout << obj.inc(y);
(1) 6
(2) 7
(3) 8
Какой результат следующего выражения ?
int *a; int b[2]; a = b;
b[0] = 7; b[1] = 10; *a++; cout << *a;
(1) 7
(2) 8
(3) 10
(4) 11
Что будет напечатано в результате выполнения следующего кода?
char x[] = "Hello, world";
char* p = x; p += 4; cout << *p;
(1) o
(2) o, world
(3) Hello, world
Комментарий в программе на Си++
(1) содержит указания компилятору по настройке программы
(2) содержит пояснения к тексту и не оказывает влияния на выполнение программы
(3) должен содержать допустимые аргументы программы
Абстрактный класс – это класс, в котором
(1) есть виртуальный конструктор
(2) есть виртуальный деструктор
(3) есть хотя бы один чисто виртуальный метод
Произойдет ли ошибка при использовании следующей конструкции:
class A { const int f() { . . .}; };
g(const& A a) { a.f(); . . . }?
(1) не произойдет
(2) да, ошибка компиляции
(3) да, ошибка выполнения
Если в классе операция delete переопределена как
void operator delete(int x, void* addr)
то при вызове
A* ptr; . . . delete(10, ptr);
(1) ошибка компиляции
(2) освободится массив из 10 элементов типа a
(3) аргумент 10 будет передан в оператор delete
Какие операции поддаются перегрузке?
(1) только унарные
(2) только бинарные
(3) унарные и бинарные
Для чего предназначен фрагмент текста из заголовочного файла:
//Пример файла test.h
#ifndef TEST
#define TEST
//прочие строки кода
#endif
(1) для целей отладки
(2) для определения символьной константы Test
(3) для защиты от повторного включения файла test.h
(4) для защиты от копирования
(5) для защиты от удаления
Могут ли контексты быть вложенными?
(1) могут
(2) не могут
(3) могут, при определенных условиях
Оператор throw без аргументов
(1) повторно вызывает обрабатываемую исключительную ситуацию
(2) вызывает последнюю необработанную исключительную ситуацию
(3) вызывает исключительную ситуацию типа Exception
Результат работы программы:
#include <iostream.h>
int main()
{
char A[] = "ABC ";
char *U = &A[2];
cout << "n" << *U--;
cout << *U-- ;
cout << *U;
return 1;
}
(1) ABC
(2) BC
(3) CBA
(4) BCA
Отметьте все неверные утверждения:
(1) генерация класса из шаблона создает объект этого класса
(2) память, занимаемая объектом класса, сгенерированного из шаблона, освобождается автоматически
(3) шаблон не является классом
(4) все классы-шаблоны наследуют класс Template
(5) объекты классов, сгенерированных из шаблонов, занимают в памяти меньше места, чем объекты других классов
Является ли x переменной или константой?
y = 12 + x;
(1) является переменной
(2) является константой
(3) определить нельзя
Нелогической операцией является
(1) &&
(2) ==
(3) ||
(4) !
(5) =
Что означает запись while (false);?
(1) бесконечный цикл
(2) цикл, который не выполняется ни разу
(3) ошибка компиляции
(4) аварийный выход из программы
Если функция имеет тип void, то неверно, что
(1) она не может содержать оператор return
(2) оператор return в теле функции необязателен
(3) функция не возвращает никакого значения
Укажите в каких строках кода произойдет ошибка компиляции?
1: class A
2: { public:
3: int x;
4: int GetX() { return x; };};
5: int main()
6: { A b;
7: b.SetX(2);
8: b.GetX();
9: A b1(2);
10:}
(1) в восьмой
(2) в четвертой
(3) в седьмой
(4) в девятой
Какая операция позволяет получить значение, записанное по адресу, который содержится в указателе?
(1) *
(2) ?
(3) ^
(4) &
(5) .
Что произойдёт при использовании неправильного адреса в операции delete?
(1) произойдёт аварийное завершение программы
(2) программа выдаст сообщение,что память освобождается по неправильному адресу
(3) результат непредсказуем
При выходе из функции main
(1) программа повторяется с теми же аргументами
(2) программа заканчивается
(3) выполняется функция finish, определенная программистом
Произойдет ли ошибка при компиляции этого кода?
class Channel
{ public:
void SetNumber (int n) { number = n;};
int GetNumber() const { return number;};
int number;};
int main()
{ private : int n;} Channel ch;
{Channel ch;
ch.number = 9;
ch.SetNumber(10);
Channel ch2(2);}
(1) нет, не произойдет
(2) да, произойдет, запись неверна
(3) произойдет при обнулении n
(4) произойдет при n=10
Какое из следующих объявлений является объявлением неизменяемого указателя?
(1) int const* ptr;
(2) const int* ptr;
(3) int * ptr const;
(4) int* const ptr;
Какой класс может использоваться в качестве типа атрибута класса?
(1) базовый класс данного класса
(2) производный от данного класса
(3) пользовательский класс
(4) произвольный класс
Если определена операция вычитания для двух объектов класса A, а операция преобразования к int не определена, что будет вызвано при
A a1,a2,a3=5;
a3 = a1 – a2;
(1) преобразование к целому
(2) операция вычитания, а затем преобразование к целому
(3) только операция вычитания
(4) произойдет ошибка
Какова последовательность создания исполняемого файла:
.cpp.(1) 1-3-2
(2) 2-3-1
(3) 1-2-3
(4) 3-2-1
(5) 2-1-3
Может ли нестатический метод иметь доступ к статическим методам и атрибутам?
(1) может
(2) не может
С помощью какого метода можно изменить текущую позицию в файле?
(1) put
(2) open
(3) seekp
Сколько параметров может быть у шаблона при определении шаблона функции ?
(1) 1
(2) столько, сколько аргументов у функции
(3) столько, сколько типов используется для параметризации
Отметьте все неправильные определения констант:
(1) const int 12X = 12;
(2) const int DAYS_OF_WEEK = 7;
(3) int const x = 2i+56;
Чему равно значение целой переменной при вычислении выражения 21/5*3?
(1) 13.02
(2) 1.47
(3) 12
(4) 1
(5) другое значение
Что выведет следующая программа?
#include <iostream>
int main() {
int i;
for(i = 0; i < 9; i++)
std::cout << i+1;
return 0;
}
(1) цифры от 0 до 8
(2) цифры от 1 до 9
(3) программа не будет построена из-за ошибок
Как называется функция, которая вызывает саму себя?
(1) конструктором
(2) деструктором
(3) подставляемой
(4) рекурсивной
Укажите в каком выражении правильно определена переменная в шестнадцатеричной системе счисления?
(1) short x = 0x1244;
(2) char c = 9340x;
(3) unsigned long l = 0x84GAF;
(4) int k = 0xCDeF;
При определении метода запись this-> говорит о том, что:
(1) атрибут принадлежит объекту, получившему сообщение
(2) атрибут принадлежит классу, определенному в заголовочном файле
(3) атрибут не принадлежит какому-либо классу
Укажите какой результат будет у следующего примера?
float array[5]; array[1] = 10; array[2] = 3;
float array2[5]; array2 = array;
cout << array2[1];
(1) 10
(2) 3
(3) ошибка компиляции
(4) 0
В каком случае программа выведет строку на консоль
(1)
#include <iostream.h>
using namespace std;
void main()
{
cout < "Hello, world!" < endl;
return;
}
(2)
#include <iostream.h>
using namespace std;
void main()
{
cout >> "Hello, world!" >> endl;
return;
}
(3)
#include <iostream.h>
using namespace std;
int main()
{
cout << "Hello, world!" << endl;
return 1;
}
Укажите какое из выражений будет правильным?
class A
{ public : int a, b;
protected : int z;
private : short i; } A1;
class B : public A
{ public : int c, d;
private : int k;} B1;
(1) A1.i = 10;
(2) B1.k = A1.i;
(3) B1.c = A1.a;
Какой правильный вызов функции базового класса из объекта производного класса, если в производном классе эта функция была замещена?
(1) FunctionName();
(2) Base::FunctionName();
(3) Base.FunctionName();
(4) такую функцию вызывать нельзя.
Отметьте правильный вариант освобождения всей памяти, выделенной для трехмерного массива для следующей программы
long (*lp)[2][4];
lp = new long[3][2][4];
(1) delete [] lp;
(2) delete lp;
(3) delete [][] lp;
(4) delete [][][] lp;
Какое приведение типов используется в следующем выражении?
int a = 0; float f = 3.4; f += (int)a;
(1) неявное приведение типов
(2) явное приведение типов
(3) стандартное приведение типов
В чем различие использования следующих выражений #include <...> и #include "..."
(1) нет различий
(2) различие заключается в методе поиска препроцессором включаемого файла
(3) в различии использования заголовочных и исходных файлов
Для чего предназначен оператор namespace?
(1) для заключения в группу объявлений классов, переменных и функций в отдельный контекст со своим именем
(2) для заключения в группу объявлений классов, переменных и функций для использования только в текущем модуле
(3) для использования классов, переменных и функций из других модулей программы без использования заголовочных файлов
Какие требования предъявляются к классу исключительных ситуаций?
(1) он должен быть наследован от специального класса exception
(2) он не может использовать множественное наследование
(3) он должен содержать атрибуты только встроенных типов
(4) он может быть произвольным классом
Что означает cout << setw(3) ?
(1) ширина поля вывода устанавливается равной 3
(2) выводимые строки сокращаются до 3 символов
(3) выводимые строки дополняются до 3 символов
(4) нельзя ввести больше 3 символов за один раз
Какой правильный вариант описания шаблона семейства классов?
(1)
template <class T>
class Array
{. . . };
(2)
template (class T)
class Array
{. . . };
(3)
template {class T}
class Array
{. . . }
В каких выражениях используются унарные арифметические операции?
(1) c1 + d2
(2) s2 % d % 2
(3) --b
(4) d++
Укажите каким будет результат вычисления цикла?
int m = 2, n = 5;
while (m <= 3)
{
while (m <= n)
{
n = m;
break;
}
break;
m++;
}
(1) m = 2; n = 10;
(2) m = 5; n = 2;
(3) m = 2; n = 2;
(4) m = 120; n = 30;
(5) m = 0; n = 2;
Укажите правильный вызов функции, объявленной следующим образом: void Iterat(int a, float b, short c);
(1) int res = 0; res = res + Iterat(2,4,34);
(2) int k = 1; float l = 3; short m = 4; Iterat(k,l,m);
(3) float d = Iterat(2,3.2,6);
Укажите правильный доступ к членам класса:
class my
{ public: double Z;
int f(int c, int d) {return c+d;}
char s;
} T1, T2;
(1) T1->Z = 23.1;
(2) T2.f(4,1);
(3) my.T2->s = 'L';
Какое значение будет выведено в стандартный поток в результате выполнения следующего фрагмента программы?
...
// Необходимые библиотеки подключены
struct my
{
int a, b;
} m1;
int func(my *f)
{
return f->a + f->b++;
}
int main()
{
m1.a = 2; m1.b = 5;
cout << func(&m1);
return 1;
}
(1) 7
(2) 8
(3) 1
(4) ошибка компиляции
Определите результат выполнения следующего кода:
float *thingPtr = new float (3.14159)
(1) возникнет ошибка компиляции, поскольку нельзя задавать значение переменной в процессе её создания
(2) возникнет ошибка компиляции, поскольку при создании объекта не указан размер выделяемой памяти
(3) данная строка задает значение объекту типа float
Файл имеющий имя "test_file.cpp" это:
(1) исполняемый файл
(2) заголовочный файл
(3) файл исходного текста языка C++
(4) динамически загружаемая библиотека
В чем заключается принцип полиморфизма?
(1) в наличии виртуальных методов
(2) в наличии множественного наследования
(3) в использовании виртуального наследования
Укажите правильное использование оператора friend
(1)
class A { friend int CountPass(); private: short i;};
(2)
class A { public : friend int A::CountPass(); private: short i;};
(3)
class A { public : int A1::CountPass(); friend: short i;};
Известно, что в классе A определен один публичный конструктор A(int);. Выберите из предложенных выражений компилируемые:
(1) A *a(4);
(2) A a;
(3) A *a = new A(4);
(4) A a(4);
Какой результат будет у следующего выражения?
int m = 1,n=2;
int *p= &n;
p=static_cast<int*> (m);
cout << *p;
(1) 1
(2) адрес переменной m
(3) 2
(4) ошибка компиляции
Какой результат будет у следующего выражения?
#define CIRC(x) (3 * (x) * (x))
#include <iostream.h>
int main()
{
int a = 1, b = 2;
std::cout << CIRC(a + b);
}
(1) 9
(2) 27
(3) 16
(4) 28
Что будет на экране после выполнения программы
func() { int a = 10; }
int main()
{ int x2 = a + 1;
cout << x2;
return 0;
}
(1) 11
(2) 1
(3) 12
(4) ошибка компиляции
Что будет на экране после выполнения программы
#include <iostream.h>
short x = 4, i = 0;
void fun1()
{ if (i == 5) throw 2; }
void fun2()
{ --x; fun1(); x++; }
int main()
{ try
{ fun2(); }
catch (int)
{ cout << "Exception "; }
cout << x << " " << i;
}
(1) Exception
(2) Exception 4 0
(3) Exception 3 0
(4) 4 0
Укажите основные используемые манипуляторы потоков.
(1) flush
(2) endl
(3) printf
(4) setw
(5) <<
(6) setprecision
В каких случаях код будет работать правильно
...
vector <int> IntVector;//объект класса вектор
/*запись значений в IntVector*/
int d=IntVector[0];
...
}
(1) если оператор [] переопределен соответствующим образом
(2) если существует элемент IntVector[0]
(3) ничего, так как эта запись ошибочна
Укажите, где происходит объявление констант или переменных
(1) float dD;
(2) float sd2 = 3.2;
(3) bType = 3;
(4) int k; k = 89;
В каких выражениях возвращаемое значение будет логическим?
(1) int x = 2, y = 3, z; z = x + y;
(2) (i > 3);
(3) (c == b);
В чем заключается назначение оператора перехода goto?
(1) изменяет последовательность выполнения операторов в программе
(2) необходим для передачи аргумента в функцию
(3) для наглядного отображения листинга программы
(4) для перехода из одной функции в другую
Если функция вычисления факториала n имеет прототип int fact(int n), то как будет выглядеть запись определения функции с использованием рекурсии?
1.{if (n == 1 || n == 0) return 1;
else return n * fact (n -1);}
2.{if (n == 1 || n == 0) return 1;
else return fact(n);}
(1) первый вариант
(2) второй вариант
(3) оба верны
(4) оба ошибочны
Укажите в каком выражении произойдет потеря точности
(1) int i; float x = 2.134, y = 3.14; i = x/y;
(2) float M = 235.2; double Z = 3; Z *= M;
(3) short i = 0x3; float x = 2.7, v; v = i + x;
Что будет выведено в стандартный поток вывода в результате исполнения следущей программы?
class Add
{ public: short S1;
int f(int x)
{ return S1 + ++x;}
int A(short a, short b);
} K1;
int Add::A(short a, short b)
{
this->S1 += a*b;
return this->S1;
};
int main()
{
K1.S1 = 2;
K1.f(2);
K1.A(0, 1);
cout << K1.S1;
return 0;
}
(1) 0
(2) 10
(3) 2
(4) 4
Какой массив имеет самый большой размер?
char *s1= "QWERTY";
char s2[7]= "qwerty";
char *s3= "127*27";
(1) Все массивы имеют одинаковый размер
(2) s1
(3) s2
(4) s3
Какой будет результат у данного выражения?
int f1(int & x1, int *x2) { return ++x1 + ++(*x2); }
int main()
{ int a = 7, k = 1;
k = f1(a, &k);
cout << a << " " << k;
}
(1) 7 10
(2) 8 1
(3) 8 10
Укажите какие компиляторы языка Си++ могут быть использованы в системе Unix.
(1) Microsoft© Visual C++
(2) Emacs
(3) GNU C++, g++, c++
(4) любой компилятор языка С++
Какой результат будет у следующего выражения?
class A
{ public : int a, b;
protected : int z;
private : short i; } A1;
class B : protected A
{ private : int k;} B1;
int main()
{ B1.z = 20;
cout << B1.z;
}
(1) 20
(2) ошибка
(3) 0
На какой строке произойдет ошибка компиляции?
1:class A
2: { public: void f1(int &a){val+=a++;};//val инициализируется в конструкторе
3: int f2() {return val+1;};
4: int val;
5: } A1;
6:int main()
7: {
8: A1.f1();
9: A1.f2();
10: }
(1) 3
(2) 5
(3) 8
(4) 9
В какой строчке данного кода сработает заданный в нем копирующий конструктор?
1 monstr::monstr(const monstr &М){
2 if (M.name){
3 name = new char [strlen(M.name) + 1];
4 strcpy(name, M.name);}
5 else name = 0;
6 health = M.health; ammo = M.ammo; skin = M.skin;
7 }
…
8 monstr Vasia (blue);
9 monstr Super = Vasia;
10 monstr *m = new monstr ("Orc");
11 monstr Green = *m;
(1) в шестой
(2) в пятой
(3) в четвертой
(4) в одиннадцатой
Если в арифметическом выражении участвуют короткое целое и длинное целое, то:
(1) длинное приводится к короткому
(2) короткое приводится к длинному
(3) ошибка компиляции
Какой результат будет у следующего выражения?
// файл File1.h
#ifndef __File_h__
#define myconst 35
#else
#define myconst 30
#endif
// файл Test.cpp
#include <iostream.h>
#include "File1.h"
int main()
{ cout << myconst + 10;
}
(1) 40
(2) 45
(3) 35
(4) ошибка компиляции
Что будет на экране после выполнения программы
int m = 5;
namespace space1
{ int x1 = 3;
namespace space2
{ int x1 = 2 + ::m + space1::x1; }
}
int main()
{ int x3 = space1::space2::x1 * 2;
{ int x3 = 10; }
cout << x3;
return 0;
}
(1) 10
(2) 20
(3) 13
(4) ошибка компиляции
Какой результат будет у следующего выражения?
int main()
{
try
{
try
{
try{ throw 1; }
catch (float) { cout << "Exception 1"; }
}
catch (int){ cout << "Exception 2"; }
}
catch (int){ cout << "Exception 3"; }
return 0;
}
(1) Exception 1Exception 2Exception 3
(2) Exception 1Exception 2
(3) Exception 1
(4) Exception 2
(5) Exception 3
Существует файл "test.dat" в котором записано "Hello World".Каково будет содержимое файла после выполнения кода:
ofstream outfile("c:\test.dat",ios::in);
if (!outfile)
{ cout << "Ошибка создания файла";
return 1; }
outfile << "!!!";
outfile.close();
(1) Hello World!!!
(2) !!!lo World
(3) !!!
Что будет выведено на экран?
template <class T> class A
{ public : T x1; T x2;
T func(T x3)
{ x3 += x1 + x2;
return x3;
}
};
int main()
{ A <int> A1;
A1.x1 = 3; A1.x2 = 10;
cout << A1.func(1);
....
}
(1) 10
(2) 14
(3) 11
(4) ошибка компиляции
Отметьте истинные высказывания:
(1) переменная объявляется, потом изменяется
(2) переменная инициализируется, потом объявляется
(3) переменная объявляется, потом инициализируется и изменяется
Чему будет равен результат вычисления выражения: int d=5; bool b = true, c; c = (!b||(d>3));
(1) true
(2) Ошибка компилятора
(3) false
Что будет выведено на экран в результате выполнения кода?
int a=3;
if (a>1) cout << "а>1";
if (a>2) cout << "a>2";
if (a>3) cout << "a>3";
(1) a>1a>2a>3
(2) a>1a>2
(3) a>1
Произойдет ли ошибка компиляции если функцию
int sum(int a, int a1, int a2, int a3, int a4=2)
{return a+a1+a2+a3+a4;}
вызвать в функции main следующим образом
int main()
{
int z=1,b=1,c=1,d=1,e=1;
sum(z,b,c,d,e);
....
}
(1) нет
(2) да, потому что значение возвращаемое функцией не присваивается никакой переменной
(3) да, потому что мы пытаемся изменить значение аргумента по умолчанию
Если имеется объявление float f=4;double d=4;
что будет выведено на экран в результате выполнения кода
if(f>d)
cout << "f>d";
if (f==d)
cout << "f=d";
if(f<d)
cout <<"f<d";
(1) f>d
(2) f=d
(3) f<d
Вызовет ли следующее объявление ошибку компиляции
class A{
public: void sum(double s1,double s2);
int sum (int s1,int s2);
};
(1) да
(2) нет
В какой строке(строках) ошибка?
1 int f1() {
2 char ch, ch1='a', ch2='b', ch3='c';
3 ch=ch1+ch2+ch3;
4 f1=int(ch);
5 return f1;
6 }
(1) 1 и 3
(2) 1, 3, 4 и 5
(3) 4 и 5
Можно ли в шаблоне класса определить статический метод?
(1) да
(2) нет, будет ошибка компиляции
(3) да, но результат работы программы непредсказуем
Выберите правильное утверждение:
(1) язык Си++ включает богатые средства разработки для Internet
(2) разработка распределенных систем часто ведется на языке Си++
(3) программные интерфейсы к операционной системе чаще всего написаны на языке Java
Какими по умолчанию объявляются элементы объединения?
(1) private
(2) public
(3) protected
(4) по умолчанию не объявляются
Вызовет ли данный код ошибку компиляции?
class Rectangle
{
public:
int a,b;
int sum();
int square();
~rect();
};
(1) нет, все записано верно
(2) да, имя деструктора не может начинаться с маленькой буквы
(3) да, имя деструктора должно совпадать с именем класса
: Выберите наиболее правильный вариант объявления оператора += двух объектов класса A:
(1) A& operator+=(const A& a);
(2) A& operator+=(A& a) const;
(3) const A& operator+=(A& a) const;
С какой целью производится вынесение функций в отдельный заголовочный файл?
(1) для более наглядного и удобного представления функций
(2) для возможности использования в нескольких модулях программы
(3) для возможности использования в других программах
Сколько блоков catch может быть после блока try?
(1) количество блоков catch зависит от количества блоков try
(2) ни одного
(3) минимум один
Отметьте правильный вариант описания функции шаблона:
(1)
template (class T)
void change(T *p1, T *p2) { . . . };
(2)
template <class T>;
void change<T *p1, T *p2> { . . . };
(3)
template <class T>
void change(T *p1, T *p2) { . . . };
(4)
template {class T}
void change(T *p1, T *p2) ( . . . );
Укажите все ключевые слова в приведенном примере?
int calc(int a, int b, bool f)
{
if (f==1)
return a+b;
else
return a*b;
}
(1) int,calc,bool,return,if,else
(2) int,if,else,return
(3) int,bool,if,else,return
Если после выражения стоит точка с запятой, то
(1) это оператор-выражение, действие которого заключается в вычислении выражения
(2) выражение вычисляется, а его значение запоминается в специальной переменной, которую можно использовать в следующем операторе
(3) выражение вычисляется только если первой стоит операция присваивания
При выполнении фрагмента кода
int x = 3, y = 2, z = 1;
if(x >= y)
if(y <= z)
cout << "Вариант 1";
else
if(x <= z)
cout << "Вариант 2";
else
cout << "Вариант 3";
будет напечатано:
(1) Вариант 1
(2) Вариант 2
(3) Вариант 3
(4) ничего не будет напечатано
(5) программа не откомпилируется
Отметьте истинное высказывание, если вызываются подряд несколько функций:
(1) все функции выполняются одновременно
(2) после выполнения одной функции управление переходит к следующей
(3) последовательность выполнения функций определяется компилятором
Что будет на экране после выполнения данного кода
{
int ar[4];
ar[0]=1;
ar[1]=2;
ar[2]=3;
ar[3]=4;
for (int i = 0; i<=4; i++)
cout << ar[i];
}
(1) 1234
(2) код не выполнится из-за ошибки компиляции
(3) 1234 и случайное число
(4) результат невозможно предсказать
Что нужно сделать для освобождения памяти после выполнения такого кода ?
char *a; a = new char[20];
(1) delete [] a;
(2) delete a[];
(3) delete a;
Для того чтобы вывести символ новой строки, надо:
(1) закончить оператор точкой с запятой
(2) воспользоваться специальным манипулятором endl
(3) при выводе строки символов перевод строки добавляется автоматически
Функция объявлена как friend класса. Отметьте верное.
(1) функция имеет доступ к внутренним атрибутам класса
(2) функция-оператор должна иметь в качестве первого аргумента объект данного класса
(3) ключевое слово friend не оказывает влияния на функции и операторы
Есть ли преимущество выноса определения функции в отдельный файл
(1) да,при использования функции в нескольких файлах
(2) нет,это затрудняет работу программы
(3) да,можно определить функцию только один раз
Если имеется программа
int a; //в глобальном пространстве
void f() { extern int a; a = 8; }
и в функции main() выполняется
a = 3; f(); cout << a;
то какой будет результат?
(1) 0
(2) 8
(3) 3
(4) другой
Если имеется код double x; cin >> x; и вводится "12-3", то что будет в переменной x?
(1) 9.0
(2) 9
(3) 12.0
(4) 3.0
(5) другое
(6) произойдет ошибка
Исходя из данного кода какое высказывание верно?
int main()
{
int a,b,c,d;
a=1;
b=2;
c=a+b+p;
cout << p;
...
}
(1) код не верен, потому что переменным c и d не присвоены значения
(2) код верен, потому что по умолчанию все переменные имеют целочисленный тип
(3) код не верен, потому что переменная p не объявлена
Чему равен результат вычисления выражения
b – x * 3 + b
при x = 12 и b = 8 ?
(1) -20
(2) -4
(3) -124
Если i=3, какой будет результат ?
if (i == 4) cout << "aaa";
else if (i == 3) cout << "bbb";
else if (i != 3) cout << "ccc";
(1) aaa
(2) bbb
(3) ccc
(4) aaaccc
(5) bbbccc
(6) ошибка компиляции
Отметьте допустимые имена функций:
(1) _This_Function_12_x_
(2) ax%u7
(3) fffffffAAAAAA
(4) 3_pi
(5) calculateIt
(6) thisname_is_too_long_forafunction
Найдите недопустимую запись символьной константы:
(1) 'F'
(2) "22"
(3) 'Ю'
(4) 'a'
Если имеется код
class A { public: int a, b, c; };
A obj;
как обратиться к члену класса c?
(1) obj.c
(2) obj->c
(3) c.obj
(4) obj.public.c
Если объявлен тип
struct Value {
double tt; //Размер переменной типа double 64 бита
union number {
short sx; // Размер переменной типа short 16 битов
long lx; // Размер переменной типа long 32 бита
double dx; // Размер переменной типа double 64 бита
} val;
};
сколько байтов занимает один объект такого типа?
(1) 8
(2) 16
(3) 28
(4) 176
Какой будет результат вывода?
static int a; a = 9;
{
static int a = 8;
}
cout << a;
(1) 9
(2) 8
(3) 0
Текст программы можно набирать:
(1) используя только редактор Emacs
(2) только в интегрированной среде программирования
(3) используя любой текстовый редактор или в интегрированной cреде программирования
Отметьте все правильные варианты продолжения предложения: виртуальный деструктор
(1) может использоваться с абстрактными классами
(2) не нужен, если класс не имеет производных классов
(3) должен быть определен как чисто виртуальный в абстрактном классе
Если в классе A определены методы
A(int x);
operator int();
operator++(int);
то какие из них будут вызваны в следующем выражении ?
A b; static_cast <int> (b + 1);
(1) operator int(), конструктор А(int x)
(2) operator++(int), operator int(), конструктор А(int x)
(3) только конструктор А(int x)
(4) только operator int()
(5) operator++(int), конструктор А(int x)
Определение класса это
(1) объявление всех его методов и полей
(2) вызов конструктора
(3) инициализация всех его полей
Можно ли создать объект класса, у которого все атрибуты и методы – статические?
(1) да, можно
(2) вызовет ошибки компиляции
(3) вызовет ошибку выполнения
Если заданы классы
class A {... } A1;
class B : public A { ... } B1;
class C : public B { ... } C1;
то что будет выведено при выполнении оператора
throw (C1);
а обработка исключительной ситуации записана
catch (B& b) { cout << 1; }
catch (C& c) { cout << 2; }
catch (A& a) { cout << 3; }
catch (...) { cout << 4; }
(1) 1
(2) 2
(3) 3
(4) 4
(5) 1 2 3 4
(6) 2 3 4
Для того чтобы выполнить чтение из файла с произвольной позиции, надо использовать объект класса
(1) strstream
(2) ostream
(3) ofstream
(4) istream
(5) ifstream
(6) filestream
Отметьте правильный вариант описания функции шаблона:
(1)
template (class T)
void change(T *p1, T *p2) { . . . };
(2)
template <class T>;
void change<T *p1, T *p2> { . . . };
(3)
template <class T>
void change(T *p1, T *p2) { . . . };
(4)
template {class T}
void change(T *p1, T *p2) { . . . };
Отметьте ложные высказывания:
(1) идентификатор - это ключевое слово языка С++
(2) ключевые слова не могут быть именами переменных, но могут быть использованы в качестве идентификаторов
(3) ключевое слово может быть создано в процессе написания программы
Если i = 5, какой будет результат вывода
do
{
cout << (++i)++ << " ";
}
while ( i>=5 && i < 8 ) ;
(1) 6
(2) 6 8
(3) 6 7
(4) 6 7 8
Если функция вычисления суммы целых чисел, находящихся между двумя заданными (начальное и конечное включительно), имеет прототип int sum(int start, int end), запишите определение функции, используя рекурсию:
(1)
{if (end == start) return 1;
else return end + sum(start, end -1);}
(2)
{if (end == start) return start;
else return end + sum(start, end -1);}
(3)
{if (end >= start) return 0;
else return end + sum(start, end -1);}
Найдите неправильную запись набора перечисляемых значений:
(1) enum { day = -1, night = +1 };
(2) enum { const start = 0, const end = 1 };
(3) enum { a, b = 2, c, d };
Какой будет результат выполнения следующего кода?
class A {
public:
int y;
int inc(int x) { return ++y; };
int inc(short x) { return x + y; };
};
A obj; int y = 5; obj.y = 6;
cout << obj.inc(y);
(1) 6
(2) 7
(3) 11
(4) ошибка компиляции
Какой результат следующего выражения ?
int *a; int b; a = &b; b = 7; (*a)++; cout << b;
(1) 7
(2) 8
(3) не определено
(4) ошибка компиляции
Что будет выведено в результате выполнения следующего кода?
int a[4] = { 1,2,3,4};
int* p = a; cout << (*p+2) + *p;
(1) 4
(2) 6
(3) адрес памяти
(4) 10
Отметьте истинные высказывания
(1) функция main может иметь несколько аргументов
(2) функция main может не иметь аргументов
(3) функция main должна иметь как минимум один аргумент
Что содержится в записи минимального по своим возможностям класса?
(1) только конструктор
(2) только деструктор
(3) только конструктор и деструктор
(4) по крайней мере, один метод
(5) по крайней мере, один атрибут
(6) не менее одного атрибута и хотя бы один метод-конструктор класса
(7) не содержится ничего
Если указатель объявлен как
char sss = 't';
char ddd;
const char* ptr = &sss;
какое из следующих выражений верно:
(1) *ptr++;
(2) (*ptr)++;
(3) *ptr = 'k';
(4) ptr = &ddd;
Какая из перечисленных функций не может быть конструктором?
(1) String();
(2) void String()
(3) String(String &s)
(4) const String(int a)
Какое из следующих определений представляет собой правильную запись операции сложения целого числа и объекта:
(1) friend A operator+(int a1, const A& a2);
(2) friend A operator+(int a1, int a2);
(3) friend void operator+(const A& a1, int a1, A res);
Каково преимущество использования ключевого слова const вместо директивы #define?
(1) константу, определенную с помощью const, можно изменять во время работы
(2) к константе, определенной с помощью const, можно применить операции инкремента и декремента
(3) константа, определенная с помощью const, доступна в других модулях программы
(4) константа, определенная с помощью const, имеет тип, и компилятор может проследить за ее использованием в соответствии с объявленным типом
Какие из перечисленных операций не являются операциями доступа к атрибуту класса?
(1) "<-"
(2) "."
(3) ":"
Отметьте те средства языка Си++, которые относятся к диагностике ошибок
(1) возвращаемое значение функции
(2) исключительные ситуации
(3) создание объектов
(4) глобальные переменные, хранящие состояние
(5) флаг состояния объекта
(6) преобразование типов переменной
Существует файл "test.dat" в котором записано "Hello World".Каково будет содержимое файла после выполнения кода:
ofstream outfile("c:\test.dat");
if (!outfile)
{ cout << "Ошибка создания файла";
return 1; }
outfile << "!!!" << endl;
outfile.close();
(1) Hello World!!!
(2) Hello World
(3) !!!
При определении класса-шаблона
(1) он должен быть включен в общий контекст (namespace) STL
(2) он должен быть наследован от класса Template
(3) он должен быть отмечен ключевым словом template
(4) он должен включать в себя, по крайней мере, один параметр-тип
Отметьте правильное определение константы:
(1) const long BITS = 32;
(2) const bit ZERO = 0x0;
(3) const float 0Ora = 5.9787;
Чему равно значение выражения 54 << 3 ?
(1) 432
(2) 440
(3) 413
(4) 47
(5) 523
(6) 556
(7) 623
(8) другое значение
Что выведет следующая программа ?
#include <iostream.h>
int main() {
int 1_i ;
for( 1_i = 0; 1_i < 9; 1_i++)
cout << 1_i +1;
return 1;
}
(1) цифры от 0 до 8
(2) цифры от 1 до 9
(3) программа не будет построена из-за ошибок
В чем разница между фактическими и формальными параметрами?
(1) формальные параметры могут использоваться только вне тела функции, а фактические - используются как вне функции, так и внутри ее
(2) формальные параметры определены в заголовке функции, а фактические - значения, с которыми функция вызывается
(3) нет различий
Укажите в каком выражении правильно применяются операции к целочисленным типам
(1) int a = 2, b = 0x24, c; c = a + b%a;
(2) long int c <<= 2;
(3) const char mychar = 2; long s = 3; mychar /= s;
Имеется класс:
class Team
{
public:
int score;
class Player
{
public:
void GoGoGo(){ printf("Player is going to a goal.n"); }
};
void SetScore(int x){ score = x;}
void ShowScore(){printf("%dn",score);}
};
Выберите из нижеприведенных записей корректные коды для этого класса:
1. Team::Player p;
p.GoGoGo();
2. Team t;
t.Player p;
p.GoGoGo();
(1) первый
(2) второй
(3) оба верны
(4) оба ошибочны
Укажите какой результат будет у следующего примера?
float arr[3] = {10, 20, 30};
float *a = arr;
cout << a[1];
(1) 10
(2) 20
(3) 30
(4) ошибка компиляции
Какой будет результат у данного выражения?
long *L = new long;
*L = 4;
delete L;
cout << *L;
(1) 4
(2) 5
(3) ошибка компиляции
(4) непредсказуемое поведение программы, возможно, случайное число
В каком файле заголовков определён объект cout:
(1) iostream.h
(2) stream.h
(3) sysutils.hpp
Укажите какое из выражений будет правильным?
class A
{
public : int a, b;
protected : int z;
private : short i;
} A1;
class B : protected A
{
public : int c, d;
private : int k;
} B1;
(1) A1.i = 10;
(2) B1.k = A1.i;
(3) B1.c = A1.z;
(4) A1.a = B1.d;
Какой правильный вызов функции базового класса из объекта производного класса, если в производном классе эта функция не была замещена?
(1) FunctionName();
(2) FunctionName.Base();
(3) Base.FunctionName();
(4) такую функцию вызвать нельзя
Сопоставьте:
1. Конструктор –
2. Деструктор –
3. Дружественная функция –
4. Переопределение операций -
A - вызывается автоматически, как только объект класса уничтожается.
B – имеет доступ к защищенным и собственным компонентам класса, не являясь его компонентом.
C – возможность распространения действия стандартных операций на операнды, для которых эти операции первоначально в языке не предполагались.
D – используется для инициализации объектов класса.
(1) 1-D, 2-A, 3-B, 4-C
(2) 1-B, 2-A, 3-D, 4-C
(3) 1-C, 2-B, 3-A, 4-D
Какие операции используются для контроля за приведением типов?
(1) reinterpret_cast
(2) static_cast
(3) const_cast
(4) basic_cast
Возможно ли использование подобного выражения?
#define myincl "D:ProjectCodingCodingU.h"
#include myincl
(1) нет
(2) да
Контекст пространства имен может содержать:
(1) только объявления переменных
(2) объявления и определения переменных
(3) другие контексты
Что происходит при попытке выполнить оператор return внутри блока catch?
(1) аварийная остановка программы
(2) повторное создание обрабатываемой исключительной ситуации
(3) выход из функции
(4) ошибка компиляции
(5) ошибка выполнения
Что означает cout << flush ?
(1) вывести перевод строки
(2) вывести возврат каретки
(3) вывести символ новой строки
(4) вывести нулевой байт
(5) произвести вывод и очистку буферов
(6) закрыть выводной поток
Какой правильный вариант создания экземпляра объекта?
template <class T> class Matrix
{ public : Matrix(int, int);
~Matrix() { }
}
(1) Matrix x(4, 5)
(2) Matrix <float> x(4, 5);
(3) Matrix :: <float> x(4, 5)
Отметьте правильные объявления переменных:
(1) int sd1K_k;
(2) char float = 53.5;
(3) int x; int y; int X;
(4) float; float = y;
В сложных выражениях последовательность выполнения операций определяется:
(1) только приоритетом операций
(2) только скобками
(3) скобками, приоритетом операций, а при одинаковом приоритете ассоциативностью операций
Чему будет равна переменная k в результате вычисления цикла?
int k = 0;
do
{
k++;
if (k == 1) continue;
else break;
++k;
}
while (k < 5);
(1) k = 2;
(2) k = 0;
(3) k = 5;
(4) k = 4;
Укажите правильное объявление функции
(1) int MyFunc(double x, y, int i);
(2) void correct(double d = 3.14, double a);
(3) int sum(int j, int k, int x = 0);
Укажите какому классу принадлежит атрибут Z1
class t
{ public: double sum::Z1;} C;
class sum
{ public: double t::Z1;} D;
(1) классу t
(2) обоим классам
(3) запись неверна
Что будет выведено на экран в результате следующего выражения?
struct my
{
int a, b;
} m1;
int func(my f)
{
return f.a + f.b++;
}
int main()
{
m1.a = 5; m1.b = 10;
cout << func(m1);
return 0;
}
(1) 15
(2) 16
(3) 5
В чём заключаются недостатки при использовании передачи аргумента в функцию по значению?
(1) затраты времени на копирование значений и затраты памяти для хранения копии при передаче больших объёмов данных
(2) невозможность изменения данных в их источнике
(3) невозможность передачи больших объёмов данных
Имеется запись: monster - базовый класс, demon - производный:
// Описываются указатели:
monster *p;
demon *d;
При выполнении какого выражения всегда можно говорить,что потери информации не будет?
(1) d=p
(2) d=(demon*)p
(3) p=d
(4) (monster*)d=p
Допустима ли следующая конструкция?
class A {
int x;
int f(const A& a)
{
x = 0;
x += a.x;
return x;
}
};
(1) допустима
(2) произойдет ошибка компиляции
(3) произойдет ошибка при выполнении
Если в классе определяется операция delete, то
(1) только одна операция delete может быть определена для класса
(2) стандартная операция delete всегда доступна для этого класса
(3) все виды операций delete должны быть определены
Переопределение операции сложения приведет к(отметьте все правильные варианты)
(1) ее вызову при выполнении операции ++ с объектом класса
(2) ее вызову при выполнении операции сложения с объектом класса
(3) преобразованию целых чисел к объекту данного класса при выполнении сложения
(4) возможному преобразованию объектов других классов к данному при выполнении операции сложения
Что будет выведено на экран в результате выполнения приведенной ниже программы:
#include "iostream.h"
#include "conio.h"
#define N=10
int main()
{
int x=N;
cout<<x;
getch();
return 0;
}
(1) компилятор выдаст ошибку компиляции
(2) N
(3) 10
(4) x
Запись ::func(x) означает, что
(1) функция func написана на ассемблере
(2) вызывается функция из анонимного контекста
(3) вызывается функция из глобального контекста
(4) вызывается функция, определенная в другом файле
(5) функция func имеет тип void
Блок try catch
(1) должен стоять в функции main
(2) заключает участок кода, в котором может сложиться исключительная ситуация
(3) должен заканчиваться catch (...)
(4) может быть повторен несколько раз в одной функции
(5) не может быть вложенным
Правильный вариант программы, выводящей "Hello World":
(1)
#include <iostream.h>
int main()
{
cout<<"Hello World";
return 0;
}
(2)
#include <iostream.h>
int main()
{
cout>>"Hello World";
return 0;
}
(3)
#include <iostream.h>
{
cout<<"Hello World";
return 0;
}
Что будет делать функция find(arr+2,arr+ARR_SIZE,5)?
(1) искать число 5 на интервале от 2 до ARR_SIZE
(2) искать 5 в массиве arr начиная от второго элемента
(3) ничего, эта запись ошибочна
Отметьте константу в следующем фрагменте кода:
int k = 3;
const int a = 2;
int m = k + a – 3;
(1) k
(2) m
(3) a
Битовой операцией является
(1) =
(2) !=
(3) ||
(4) +
(5) &
Отметьте ошибочное утверждение:
(1) Си++ обеспечивает строгий контроль типов
(2) строгое согласование по типам между формальными и фактическими параметрами требует, чтобы в модуле до первого обращения к функции было помещено либо ее описание, либо определение
(3) при обращении к функции фактические параметры заменяются формальными
Каким будет результат следующей программы:
int a = 5*3;
float b = 1.5f;
b += --a/2;
cout << b;
(1) 8.0
(2) 9.0
(3) 8.5
(4) 9.5
(5) 7.5
В каких выражениях правильно определен метод класса Ping?
class Ping
{ public: float f, d;
int a;
void MathFunc(double D);
};
(1) Ping::MathFunc(double D) {f = D;}
(2) void Ping::MathFunc(double D) {f = D;}
(3) void Ping::MathFunc(double D) {this->f = D;}
Что будет выведено на экран в результате выполнения приведенной ниже программы?
#include "iostream.h"
#include "conio.h"
int main()
{
int *a;
int b=7;
a = &b;
b+= 7;
(*a)++;
cout << a;
getch();
return 0;
}
(1) 14
(2) адрес переменной b
(3) компилятор выдаст ошибку компиляции
(4) адрес указателя a
(5) нет правильного ответа
Что произойдёт если операция выделения памяти new завершится неудачно?
(1) произойдёт аварийное завершение программы
(2) программа выдаст сообщение о невозможности выделения памяти под данный объект и вернёт ненулевой указатель
(3) выделение памяти под объект не произойдёт, и операция new вернёт нулевой указатель или будет сгенерировано исключение
Отметьте истинное утверждение для абстрактного класса.
(1) класс, у которого есть хотя бы один чисто виртуальный метод, называется абстрактным
(2) абстрактный базовый класс навязывает определенный интерфейс всем производным из него классам
(3) невозможно создать объект абстрактного класса
(4) в абстрактном классе нельзя определять методы
Какой результат будет у следующего выражения?
class A
{ CountPass();
private: short i;
}A1;
friend A::CountPass()
{ A1.i = 23;}
(1) объявление дружественной функции для всех производных класса A
(2) инициализация атрибута класса
(3) ошибка компиляции
В каком порядке происходит вызов деструкторов при уничтожении объекта производного класса?
(1) вызывается деструктор производного класса, затем деструкторы атрибутов производного класса и, потом, деструктор базового класса
(2) вызывается деструктор базового класса, затем деструкторы атрибутов базового класса и, потом, деструктор производного класса
(3) вызывается деструктор базового класса, затем деструктор производного класса и, потом, деструкторы атрибутов базового класса
Какой результат будет у следующего выражения?
int m = 10, n = 4;
double A = static_cast <double> (m)/n;
cout << A;
(1) 2
(2) 2.5
(3) 1
(4) ошибка компиляции
Отметьте истинные высказывания в отношении использования макросов и функций:
(1) в большинстве случаев функции позволяют сокращать объем выполняемого файла
(2) в большинстве случаев макросы позволяют сокращать время выполнения
(3) недостатком макросов является отсутствие встроенного контроля согласования типов аргументов и формальных параметров
Что будет на экране после выполнения программы
namespace t
{ char * c = "Hi"; }
int main()
{
char * x2 = c;
cout << x2;
return 0;
}
(1) Hi
(2) H
(3) ошибка компиляции
Что будет на экране после выполнения программы
#include <iostream.h>
short x = 4, i = 0;
void fun1()
{ double p=2;
if (!i) throw p; }
void fun2()
{ --x; fun1(); x++; }
int main()
{ try
{ fun2(); }
catch (double)
{ cout << "Exception "; }
cout << x << " " << i;
}
(1) Exception
(2) Ошибка компиляции
(3) Exception 3 0
(4) 4 0
Укажите правильное объявление шаблона функции, если в программе производится вызов double х = zero<double>();
(1) template <class TT> TT zero() { return 0;}
(2) template TT zero() { return 0;}
(3) template <class TT> TT zero { return 0;}
В каком случае компилятор выдаст ошибку:
(1) int int iCeloe;
(2) const float fL = 32; float e23 = 1; fL = e23;
(3) bool LD1LW;
В каком случае выражение вычислится быстрее:
(1) X = X + Y
(2) X += Y
(3) X = Y + X
В каких случаях произойдет ошибка компиляции?
(1)
void f(int i)
{ return i++;}
(2)
int ZZ(float i, int w = 2)
{ i += w;
return;
}
(3)
int x(int w)
{ int x = w;
return w;
return x++;
}
Укажите в каких выражениях неправильно применяются операции к вещественным типам
(1) double a = 2.3, b = 242, c; c = 23 + b%a;
(2) float xx = 0, z = 2; if (z >= xx) {...}
(3) float k = 44; k >>= 2;
(4) double dd = 2; long double s = 3; if (s ^ dd) {...};
Какое второе число будет выведено в результате выполнения следующего кода?
class t
{ public: int sum;
float f(int a, short m)
{
sum++;
return sum * a - m;
}
} v;
int main()
{
v.sum = 5;
cout << v.sum << " " << v.f(5, 2);
}
(1) 27
(2) 28
(3) 29
(4) 5
(5) 6
Вернет ли фукция strcmp("qwerty","QWERTY"); 0?
(1) нет
(2) да
(3) результат предсказать невозможно
Что будет выведено в результате выполнения данного кода?
int f1(int x1, int &x2) { return ++x1 + (++x2); }
int main()
{ int a = 7, k = 1;
k = f1(a, k);
cout << a << " " << k;
}
(1) 7 1
(2) 8 10
(3) 7 10
Что выведет программа в стандартный поток вывода?
class A
{
public : int a, b;
protected : int z;
private : short id;
} A1;
class B : protected A
{ public : short t;
void ff(short a)
{ id = ++a; }
} B1;
int main()
{
B1.ff(20);
cout << B1.id;
}
(1) 20
(2) 21
(3) 0
(4) ошибка
В какой строке будет ошибка компиляции?
1:class A
2: { public: void f1(int &a){val+=a++;};//val инициализируется в конструкторе
3: int const f2() {return val+1;};
4: int val;
5: void f3(int f, const char ch);
6: } A1;
7: void A::f3(int f, const char ch){
8: int d=5;
9: f1(*d);
10: f2();
11: }
(1) 3
(2) 6
(3) 9
Отметьте истинное высказывание для данного примера:
Item::Item() : taken(false), invNumber(0)
{ }
(1) метод класса Item выделяет память под атрибуты класса taken и invNumber
(2) происходит инициализация атрибутов класса taken и invNumber
(3) происходит инициализация методов класса taken и invNumber
Какой результат будет у следующего выражения?
const char* str1 = "hello";
char* str2 = const_cast ( char* ) str1;
cout << str2;
(1) hello
(2) h
(3) hellohello
(4) ошибка компиляции
Если задано
#define f(x) x##4
то какой будет результат после препроцессора ?
#if 4
int d4;
f(d) = 5;
#endif
(1) d4 = 5;
(2) x*4 = 5;
(3) пусто
(4) f(d4) = 5;
(5) d = x;
(6) = 5;
Что будет на экране после выполнения программы
int m = 5;
namespace space1
{ int x1 = 1;
namespace space2
{ int x1 = 3 + ::m + space1::x1; }
}
int main()
{ int x3 = space1::space2::x1 * 2;
{ int x3 = 20; }
cout << x3;
return 0;
}
(1) 20
(2) 18
(3) 4
(4) ошибка компиляции
Что будет выведено на экран после выполнения программы?
int main()
{
try{
try
{ throw 3.14; }
catch (int) { cout << "Exception 1"; }
}
catch (...)
{ cout << "Exception 2"; }
return 0;
}
(1) Exception 1Exception 2
(2) Exception 1
(3) Exception 2
Существует файл "c:test.dat" в котором записано "Hello World".Каково будет содержимое файла после выполнения кода:
ofstream outfile("c:\test.dat",ios::in);
if (!outfile)
{ cout << "Ошибка создания файла";
return 1; }
outfile << "!!!";
outfile.close();
(1) Hello World!!!
(2) !!!lo World
(3) !!!
Отметьте верное утверждение:
(1) шаблон может быть членом класса или шаблона класса
(2) шаблон может быть членом только шаблона класса
(3) шаблон может быть членом только класса
(4) шаблон не может быть членом класса или шаблона-класса
Укажите в каких выражениях переменная или константа объявляется и ей присваивается значение:
(1) const bool b;
(2) float dasf3, s2, d34w;
(3) static int iINTEGER; iINTEGER = 2;
Чему будет равен результат вычисления выражения: float A = 2, B = 20, C; C = (B = A = 5) + 1;
(1) 6
(2) 20
(3) 2
В какой строке будет ошибка компиляции
1 int sum(int a,int b,int c,int d=4){
2 int result;
3 result=128*a+b-c*d;
4 d=25;
5 a=d;
6 return res;
7 }
(1) ошибок нет,все верно
(2) в 4
(3) в 5
(4) в 6
Вызовет ли следующее объявление ошибку компиляции
class A{
public: double sum(int double,double s2);
int sum (int s1,int s2);
}
(1) да
(2) нет
Что будет выведено на экран, если вызвать данную функцию последовательно три раза?
void f1() {
static int flag=0;
if (!flag) {
cout << "false ";
flag=5;
}
else {
cout <<"true ";
flag=0;
}
}
(1) false false false
(2) false true false
(3) true false true
Исходя только из назначения шаблонов имеет ли смысл делать из данного кода функцию-шаблон
if(a){
a=a%b;
}
else
cout << error;
(Желательно ответить на этот вопрос не глядя на варианты ответов)
(1) нет, данный код можно использовать только для целых чисел
(2) нет, данный код можно использовать только для величин логического типа
(3) да, данный код можно использовать для переменных типа int и char
Если в процессе компиляции программы возникла ошибка то:
(1) будет создан исполняемый файл
(2) компилятор выдаст сообщение об ошибке и создаст исполняемый файл
(3) компилятор выдаст сообщение об ошибке, с возможным указанием её места
Если задано
#define foo(x,y) x##y
то какой будет результат после препроцессора ?
foo(a,c)
(1) ac
(2) "ac"
(3) a c
(4) a#c
Какое правильное объявление виртуальной функции, которая принимает одно целочисленное значение и возвращает void:
(1) void SomeFunction(int);
(2) virtual SomeFunction(int);
(3) virtual SomeFunction();
(4) virtual void SomeFunction(int);
Отметьте верное утверждение:
(1) типизированная переменная может изменять свой тип во время исполнения программы
(2) среда разработки использует эту переменную для своего внутреннего использования
(3) любая переменная в программе должна принадлежать какому-то определенному типу
Об ошибке в конструкторе класса может сигнализировать:
(1) возвращаемое значение
(2) исключительная ситуация
(3) вызов деструктора сразу в конструкторе
(4) установленный атрибут-флаг объекта
Каким будет результат следующей программы:
int a = 5/3;
float b = 1.5f;
b += --a/2;
cout << b;
(1) 2.5
(2) 1.5
(3) 6.0
(4) 1.0
(5) 0.0
(6) -0.5
Если i = 5, какой будет результат?
while (i <=5)
{
cout << (--i)-- << " ";
if ( i < 2) break;
}
(1) ошибка компиляции
(2) цикл ни разу не будет выполнен
(3) цикл будет выполняться бесконечно
(4) 4 3 2 1
(5) 4 3 2
(6) 4 2 1
(7) 4 2
Какой результат вычисления следующего выражения?
0xFF & 5 >> 1 + 1
(1) 1
(2) 2
(3) 256
Какой из ниже перечисленных вариантов не является формой записи вещественного числа?
(1) 12.3e+2
(2) 1.23
(3) .1f
(4) 0x3F
Двумерный массив Ar представленный в виде матрицы имеет вид
_ _
| 1 2 3 |
| 5 6 7 |
|_ 9 1 2 _|
Что будет на экране в результате выполнения кода
int flag=7;
for (int i = 0; i<3; i++) {
if (flag)
cout << Ar[1][i];
else
cout << "Error";
}
(1) 159
(2) код не выполнится из-за ошибки компиляции
(3) 567
(4) Error
Какое выражение верно с точки зрения целесообразности использования динамического распределения памяти?
(1) char* c = new char[5]; c = "aabb";
(2) char* c = "aabb";
(3) char c; c = new string("aabb");
Что выведет следующая программа ?
#include <iostream.h>int main() { int 1_i ; for( 1_i = 0; 1_i < 9; 1_i++) cout << 1_i +1; return 1;}
- цифры от 0 до 8
- (Правильный ответ) программа не будет построена из-за ошибок
- цифры от 1 до 9
Существует файл «test.dat» в котором записано «Hello World».Каково будет содержимое файла после выполнения кода:
ofstream outfile(«»c:\test.dat»»); if (!outfile) { cout << «»Ошибка создания файла»»; return 1; } outfile << «»!!!»» << endl; outfile.close();
- (Правильный ответ) !!!
- Hello World!!!
- Hello World
Вызовет ли данный код ошибку компиляции?
class Rectangle{ public: int a,b; int sum(); int square(); ~rect(); };
- да, имя деструктора не может начинаться с маленькой буквы
- нет, все записано верно
- (Правильный ответ) да, имя деструктора должно совпадать с именем класса
Для того чтобы вывести символ новой строки, надо:
- (Правильный ответ) воспользоваться специальным манипулятором endl
- при выводе строки символов перевод строки добавляется автоматически
- закончить оператор точкой с запятой
Какой будет результат выполнения следующего кода?
class A {public: int y; int inc(int x) { return ++y; }; int inc(short x) { return x + y; };};A obj; int y = 5; obj.y = 6;cout << obj.inc(y);
- 11
- ошибка компиляции
- 6
- (Правильный ответ) 7
В каком случае описание класса верно?
1. class A{ public: int x; int summ(int a){return x+a;}};2. class my_cl{ public: int f; int summ(int a){return x+a;}};3. class B{ public: int F; void Ch_F(int x) { F=x; return F; }};
- в третьем
- (Правильный ответ) в первом
- во втором
Функция вычисляет произведение двух чисел. Исходные данные вводятся с клавиатуры. Какие проверки целесообразно ввести в программе?
- проверка, что исходные данные являются числами и эти числа больше нуля
- (Правильный ответ) проверка, что исходные данные являются числами
- проверка исходных данных на равенство нулю
- проверки не нужны, все возможные ошибки отловит компилятор
Что выведет программа в стандартный поток вывода?
class A{ public : int a, b; protected : int z; private : short id; } A1;class B : protected A{ public : short t; void ff(short a) { id = ++a; }} B1;int main(){ B1.ff(20); cout << B1.id;}
- 20
- 0
- 21
- (Правильный ответ) ошибка
Для чего предназначен оператор namespace?
- для заключения в группу объявлений классов, переменных и функций для использования только в текущем модуле
- (Правильный ответ) для заключения в группу объявлений классов, переменных и функций в отдельный контекст со своим именем
- для использования классов, переменных и функций из других модулей программы без использования заголовочных файлов
Какие компоненты могут входить в интегрированную среду программирования
- (Правильный ответ) компилятор
- (Правильный ответ) текстовый редактор
- (Правильный ответ) отладчик
Укажите все ключевые слова в приведенном примере?
int calc(int a, int b, bool f) { if (f==1) return a+b; else return a*b; }
- (Правильный ответ) int,bool,if,else,return
- int,calc,bool,return,if,else
- int,if,else,return
Если определена операция вычитания для двух объектов класса A, а операция преобразования к int не определена, что будет вызвано при
A a1,a2,a3=5;a3 = a1 – a2;
- только операция вычитания
- преобразование к целому
- (Правильный ответ) произойдет ошибка
- операция вычитания, а затем преобразование к целому
Шаблон A и его специализации объявлены следующим образом:
template <class T> class A{ public: A(){ printf(«»1 «»);}};template <> class A<int>{ public: A(){ printf(«»2 «»);}};template <> class A<char*>{ public: A(){ printf(«»3 «»);}};
Какой будет результат после выполнения кода
A<int> a;A<char> a1;A<long> a2;
- ошибка компиляции в строке «template <> class A<int>»
- 2 3 1
- 1 1 1
- (Правильный ответ) 2 1 1
Что будет на экране после выполнения программы
int m = 5; namespace space1 { int x1 = 1; namespace space2 { int x1 = 3 + ::m + space1::x1; } } int main() { int x3 = space1::space2::x1 * 2; { int x3 = 20; } cout << x3; return 0; }
- (Правильный ответ) 18
- 20
- ошибка компиляции
- 4
Какой из наборов перечисляемых значений записан правильно?
- enum { a, b = 3, c = 4, 3 };
- (Правильный ответ) enum {a, b = 3, c, d };
- enum { a, b, 3, 4 };
В чем различие использования следующих выражений #include <…> и #include «…»
- в различии использования заголовочных и исходных файлов
- нет различий
- (Правильный ответ) различие заключается в методе поиска препроцессором включаемого файла
Чему будет равен результат вычисления выражения: int d=5; bool b = true, c; c = (!b||(d>3));
- (Правильный ответ) true
- Ошибка компилятора
- false
Если в арифметическом выражении участвуют целый и вещественный операнды, то:
- (Правильный ответ) целый тип приводится к вещественному
- ошибка компиляции
- вещественный тип приводится к целому
Что будет выведено в результате выполнения данного кода?
int f1(int x1, int &x2) { return ++x1 + (++x2); } int main() { int a = 7, k = 1; k = f1(a, k); cout << a << «» «» << k; }
- 7 1
- (Правильный ответ) 7 10
- 8 10
Что будет на экране после выполнения программы
#include <iostream.h>short x = 4, i = 0;void fun1(){ double p=2; if (!i) throw p; }void fun2(){ —x; fun1(); x++; }int main(){ try { fun2(); } catch (double) { cout << «»Exception «»; } cout << x << «» «» << i; }
- (Правильный ответ) Exception 3 0
- Ошибка компиляции
- Exception
- 4 0
Для чего предназначен фрагмент текста из заголовочного файла:
//Пример файла test.h#ifndef TEST#define TEST//прочие строки кода#endif
- (Правильный ответ) для защиты от повторного включения файла test.h
- для определения символьной константы Test
- для целей отладки
- для защиты от удаления
- для защиты от копирования
Укажите в каком выражении произойдет потеря точности
- (Правильный ответ) int i; float x = 2.134, y = 3.14; i = x/y;
- float M = 235.2; double Z = 3; Z *= M;
- short i = 0x3; float x = 2.7, v; v = i + x;
Какой результат будет у следующего выражения?
const char* str1 = «»hello»»; char* str2 = const_cast ( char* ) str1; cout << str2;
- hellohello
- h
- (Правильный ответ) ошибка компиляции
- hello
Если после выражения стоит точка с запятой, то
- выражение вычисляется только если первой стоит операция присваивания
- (Правильный ответ) это оператор-выражение, действие которого заключается в вычислении выражения
- выражение вычисляется, а его значение запоминается в специальной переменной, которую можно использовать в следующем операторе
Что из себя представляет динамическое выделение памяти?
- (Правильный ответ) память под объект (переменную) может выделяться не сразу, а в процессе работы программы, освобождение памяти производится вручную
- память под объект (переменную) выделяется каждый раз при обращении к переменной
- память под объект (переменную) может выделяться не сразу, а в процессе работы программы, освобождение памяти производится автоматически после завершения программы
Отметьте истинные высказывания:
- переменная инициализируется, потом объявляется
- (Правильный ответ) переменная объявляется, потом изменяется
- переменная объявляется, потом инициализируется и изменяется
Какой правильный вариант описания шаблона семейства классов?
- template {class T}class Array{. . . }
- (Правильный ответ)
template <class T>class Array{. . . }; - template (class T)class Array{. . . };
Какие операции поддаются перегрузке?
- только унарные
- (Правильный ответ) унарные и бинарные
- только бинарные
В каком случае программа выведет строку на консоль
- #include <iostream.h>using namespace std;void main(){ cout < «»Hello, world!»» < endl; return;}
- (Правильный ответ)
#include <iostream.h>using namespace std;int main(){ cout << «»Hello, world!»» << endl; return 1;} - #include <iostream.h>using namespace std;void main(){ cout >> «»Hello, world!»» >> endl; return;}
Если в программе уже имеется функция с прототипом int func(int k, double f), то какое из следующих объявлений не вызовет ошибки компиляции?
- void func(int m, double g = 3.14)
- (Правильный ответ) int func(double x, int y)
- double func(int m, double g)
С помощью какого метода можно изменить текущую позицию в файле?
- open
- put
- (Правильный ответ) seekp
Какой результат будет у следующего выражения?
int main() { try { try { try { throw 1; } catch (int) { cout << «»Exception 1″»; } } catch (int) { cout << «»Exception 2″»; } } catch (int){ cout << «»Exception 3″»; } return 0; }
- Exception 1Exception 2Exception 3
- Exception 3
- Exception 2
- Exception 1Exception 2
- (Правильный ответ) Exception 1
Какой правильный вызов функции базового класса из объекта производного класса, если в производном классе эта функция была замещена?
- (Правильный ответ) Base::FunctionName();
- FunctionName();
- Base.FunctionName();
- такую функцию вызывать нельзя.
Отметьте истинное высказывание, если вызываются подряд несколько функций:
- последовательность выполнения функций определяется компилятором
- все функции выполняются одновременно
- (Правильный ответ) после выполнения одной функции управление переходит к следующей
Что будет напечатано в результате выполнения следующего кода?
char x[] = «»Hello, world»»; char* p = x; p += 4; cout << *p;
- Hello, world
- o, world
- (Правильный ответ) o
Функция объявлена как friend класса. Отметьте верное.
- (Правильный ответ) функция имеет доступ к внутренним атрибутам класса
- ключевое слово friend не оказывает влияния на функции и операторы
- функция-оператор должна иметь в качестве первого аргумента объект данного класса
Какой результат будет у следующего выражения?
int m = 1, n=2; double A = (double)m/n; cout << A;
- 0
- 1
- ошибка компиляции
- (Правильный ответ) 0.5
В каких случаях код будет работать правильно
…vector <int> IntVector;//объект класса вектор/*запись значений в IntVector*/int d=IntVector[0];… }
- (Правильный ответ) если оператор [] переопределен соответствующим образом
- если существует элемент IntVector[0]
- ничего, так как эта запись ошибочна
Процесс компиляции программы
- приводит программы к единообразному внешнему виду
- (Правильный ответ) переводит исходный текст в исполняемый файл
- для языка Си++ необязателен
Какой массив имеет самый большой размер?
char *s1= «»QWERTY»»;char s2[7]= «»qwerty»»;char *s3= «»127*27″»;
- s3
- s1
- (Правильный ответ) Все массивы имеют одинаковый размер
- s2
Существует файл «c:test.dat» в котором записано «Hello World».Каково будет содержимое файла после выполнения кода:
ofstream outfile(«»c:\test.dat»»,ios::in); if (!outfile) { cout << «»Ошибка создания файла»»; return 1; } outfile << «»!!!»»; outfile.close();
- (Правильный ответ) !!!lo World
- Hello World!!!
- !!!
Является ли x переменной или константой?
y = 12 + x;
- является константой
- (Правильный ответ) определить нельзя
- является переменной
Что описывает данная строка программы: float mas=new int[3][2]?
- (Правильный ответ) данная строка представляет собой ошибочную запись и работать не будет
- создание одномерного динамического массива из 3 элементов
- создание двумерного динамического массива размерности 3*2
- создание одномерного динамического массива из 2 элементов
Может ли нестатический метод иметь доступ к статическим методам и атрибутам?
- не может
- (Правильный ответ) может
Отметьте все утверждения, которые считаете верными:
- (Правильный ответ) в качестве описания шаблона функции используется прототип шаблона: template <список _параметров _шаблона >
- (Правильный ответ) цель введения шаблонов – создание функций, которые могут обрабатывать разнотипные данные
- нельзя с помощью шаблона создать функцию с таким же именем, как у явно определенной функции
Определите результат выполнения следующего кода:
float *thingPtr = new float (3.14159)
- возникнет ошибка компиляции, поскольку нельзя задавать значение переменной в процессе её создания
- (Правильный ответ) данная строка задает значение объекту типа float
- возникнет ошибка компиляции, поскольку при создании объекта не указан размер выделяемой памяти
Если в арифметическом выражении участвуют короткое целое и длинное целое, то:
- ошибка компиляции
- длинное приводится к короткому
- (Правильный ответ) короткое приводится к длинному
Что будет выведено на экран?
template <class T> class A { public : T x1; T x2; T func(T x3) { x3 += x1 + x2; return x3; } }; int main() { A <int> A1; A1.x1 = 3; A1.x2 = 10; cout << A1.func(1); …. }
- (Правильный ответ) 14
- 11
- 10
- ошибка компиляции
Чему равно значение выражения !((1 || 0) && 0) ?
- ошибка компиляции
- 0
- (Правильный ответ) 1
Может ли статический метод класса быть объявлен как friend?
- не может
- (Правильный ответ) может
Если int n=3, какой будет результат ?
switch(n) {case 2: cout << «»ааа»»; break;case 3: cout << «»ббб»»; break;default: cout << «»ввв»»; break; }
- ошибка компилятора
- ввв
- ааа
- (Правильный ответ) ббб
- неопределенное поведение
С помощью механизма friend можно разрешить обращение к внутренним элементам класса:
- (Правильный ответ) всем методам другого класса
- (Правильный ответ) отдельной функции
- (Правильный ответ) отдельному методу другого класса
Что вычисляет эта функция:
double func(double x, int n) { if (n == 0) return 1; if (x == 0) return 0; if (n > 0) return x * func(x, n-1); if (n < 0) return func(x, n+1) / x; }
- факториал
- любую степень любого числа
- (Правильный ответ) целую степень любого числа
- дробную степень вещественного ненулевого числа
Укажите какому классу принадлежит атрибут Z1
class t { public: double sum::Z1;} C; class sum { public: double t::Z1;} D;
- обоим классам
- классу t
- (Правильный ответ) запись неверна
Что будет выведено на экран в результате выполнения кода?
int a=3;if (a>1) cout << «»1″»;else if(a>2) cout << «»2″»;else if(a>3) cout << «»3″»;
- (Правильный ответ) 1
- 12
- 123
Что будет на экране после выполнения программы
#include <iostream.h>short x = 4, i = 0;void fun1(){ if (i == 0) throw 2; }int fun2(){ —x; fun1(); x++; return x; }int main(){ try { fun2(); } catch (int) { cout << «»Exception «»; } cout << x << «» «» << i; }
- ошибка компиляции
- Exception 4 0
- Exception
- (Правильный ответ) Exception 3 0
Если функция вычисления факториала n имеет прототип int fact(int n), отметьте код, использующий рекурсию и правильно вычисляющий значение факториала:
- (Правильный ответ)
{if (n == 0 || n==1) return 1; else return n * fact (n -1);} - {if (n == 0 || n==1) return 1; else return fact(n)*(n-1);}
- {if (n == 0 || n==1) return 1; else return fact(n) * fact(n-1);}
- {if (n == 0 || n==1) return 1; else return fact(n);}
В каком случае компилятор выдаст ошибку:
- (Правильный ответ) int int iCeloe;
- bool LD1LW;
- (Правильный ответ) const float fL = 32; float e23 = 1; fL = e23;
Известно, что в классе A определен один публичный конструктор A(int);. Выберите из предложенных выражений компилируемые:
- A *a(4);
- A a;
- (Правильный ответ) A *a = new A(4);
- (Правильный ответ) A a(4);
Определите размер структуры
struct {char fio[30];unsigned char date:4;unsigned char code:4;};
- 9 байт
- (Правильный ответ) 31 байт
- 38 байт
- 39 байт
Укажите правильный идентификатор для имени переменной:
- FA_Ф12
- int
- 2a
- (Правильный ответ) _ri18
Если есть два объявления int qwerty; int QWERTY; какое из
утверждений верно
- такие имена переменных недопустимы
- (Правильный ответ) объявления правильные
- такие объявления недопустимы, так как мы пытаемся создать две одинаковые переменные
Какой статус международного стандарта языка Си++?
- (Правильный ответ) принят ISO и тем самым автоматически принят во всех странах
- принят проект стандарта, дорабатывается
- принят только в США и ждет одобрения международной организации
Битовой операцией является
- (Правильный ответ) &
- ||
- +
- !=
- =
Какой результат будет у следующего выражения?
int main() { try { try { try{ throw 1; } catch (float) { cout << «»Exception 1″»; } } catch (int){ cout << «»Exception 2″»; } } catch (int){ cout << «»Exception 3″»; } return 0; }
- Exception 1Exception 2Exception 3
- Exception 1
- Exception 1Exception 2
- (Правильный ответ) Exception 2
- Exception 3
Какое из следующих объявлений является объявлением неизменяемого указателя?
- (Правильный ответ) int* const ptr;
- const int* ptr;
- int * ptr const;
- int const* ptr;
Допустима ли следующая конструкция?
class A { int x; int f(const A& a) { x = 0; x += a.x; return x; } };
- произойдет ошибка компиляции
- (Правильный ответ) допустима
- произойдет ошибка при выполнении
Будет ли вызываться конструктор, если в программе встретится следующaя конструкция:
monstr Super(200, 300), Vasia(50), Z;monstr X = monstr(1000);monstr Y = 500;
- (Правильный ответ) да, будет
- нет, не будет
- данный код вызовет ошибку компиляции
- данная конструкция содержит синтаксическую ошибку
- обмен данными между программами
- (Правильный ответ) механизм ввода-вывода
- обмен данными между компилятором и функцией main
Что будет выведено в результате выполнения следующего кода?
int a[4] = { 1,2,3,4}; int* p = a; cout << (*p+2) + *p;
- (Правильный ответ) 4
- 6
- 10
- адрес памяти
Что будет на экране после выполнения программы
#include <iostream.h>short x = 4, i = 0;void fun1(){ if (i == 5) throw 2; }void fun2(){ —x; fun1(); x++; }int main(){ try { fun2(); } catch (int) { cout << «»Exception «»; } cout << x << «» «» << i;}
- Exception 4 0
- (Правильный ответ) 4 0
- Exception
- Exception 3 0
Могут ли контексты быть вложенными?
- могут, при определенных условиях
- не могут
- (Правильный ответ) могут
Что будет на экране после выполнения данного кода
{ int ar[4]; ar[0]=1; ar[1]=2; ar[2]=3; ar[3]=4; for (int i = 0; i<=4; i++) cout << ar[i]; }
- результат невозможно предсказать
- 1234
- код не выполнится из-за ошибки компиляции
- (Правильный ответ) 1234 и случайное число
Чему равен результат вычисления выражения
b – x * 3 + b
при x = 12 и b = 8 ?
- -4
- (Правильный ответ) -20
- -124
Если имеется код
class A { public: int a; };A obj;
как обратиться к переменной a?
- (Правильный ответ) obj.a
- obj-a
- obj::a
В каких выражениях правильно определен метод класса Ping?
class Ping { public: float f, d; int a; void MathFunc(double D);};
- Ping::MathFunc(double D) {f = D;}
- (Правильный ответ) void Ping::MathFunc(double D) {this->f = D;}
- (Правильный ответ) void Ping::MathFunc(double D) {f = D;}
В чем заключается принцип полиморфизма?
- в использовании виртуального наследования
- (Правильный ответ) в наличии виртуальных методов
- в наличии множественного наследования
Какие бывают конструкторы?
- инициализирующий
- (Правильный ответ) по умолчанию
- (Правильный ответ) с параметрами
- (Правильный ответ) копирующий
Какой результат следующего выражения ?
int *a; int b[2]; a = b; b[0] = 7; b[1] = 10; *a++; cout << *a;
- (Правильный ответ) 10
- 7
- 8
- 11
Что является результатом компоновки программы?
- набор заголовочных файлов с определением в них всех используемых функций
- (Правильный ответ) исполняемый файл или библиотека
- заголовочный файл
Если имеется объявление char ch1=’a’,ch2=’b’,ch3=’c’;
допустима ли запись ch1=ch2+ch3;
- нет
- (Правильный ответ) да
Если объявлен тип
struct Value {double tt; //Размер переменной типа double 64 битаunion number { short sx; // Размер переменной типа short 16 битов long lx; // Размер переменной типа long 32 бита double dx; // Размер переменной типа double 64 бита } val;};
сколько байтов занимает один объект такого типа?
- 176
- (Правильный ответ) 16
- 28
- 8
Если функция вычисления суммы целых чисел от 1 до n имеет прототип int sum(int n), запишите определение функции, используя рекурсию:
- (Правильный ответ)
{if (n == 1) return 1; else return n + sum (n – 1);} - {if (n == 1) return 1; else return sum(n);}
- {if (n == 1) return 1; else return sum(n) + sum (n-1);}
- {if (n == 1) return 1; else return sum(n) + (n – 1);}
Что происходит при попытке выполнить оператор return внутри блока catch?
- (Правильный ответ) выход из функции
- повторное создание обрабатываемой исключительной ситуации
- ошибка компиляции
- аварийная остановка программы
- ошибка выполнения
Чему равен результат вычисления выражения
x + 3 * b + x
при x = 12 и b = 8 ?
- 132
- 300
- (Правильный ответ) 48
Что выполняет операция «delete [] v;» в данном ниже коде:
class MyClass { int sz; // число элементов int * v; // указатель на целые public: MyClass ( int ); ~MyClass (); int&operator [] ( int index ); // операция индексации };…MyClass::~ MyClass() { delete [] v; }
- удаляет последний элемент из массива «v»
- (Правильный ответ) удаляет весь массив «v», освобождая память
- удаляет указатель на массив «v» из памяти
- удаляет первый элемент из массива «v»
Е
Имеется объект класса CreditCard -MyCard, который
описывает вашу кредитную карту. Класс
CreditCard содержит следующие методы и поля
Абстрактный класс – это класс, в котором
Битовой
операцией является
Блок try
catch
Будет ли вызываться конструктор, если
в программе встретится следующaя
конструкция:
В каких выражениях используются бинарные
арифметические операции?
В каких выражениях используются унарные
арифметические операции?
В каких выражениях правильно определен
метод класса Ping?
В каких выражениях произойдет зацикливание
программы?
В каких выражениях результатом будет
логическое значение?
В каких случаях возможность прокрутки
окна фрейма будет предоставляться при
необходимости?
В каких случаях код будет работать
правильно
В каких случаях произойдет ошибка
компиляции?
В каких строках ошибка?
В какой строке будет ошибка компиляции
В какой строке(строках) ошибка?
В какой строчке данного кода сработает
заданный в нем копирующий конструктор?
В каком порядке происходит вызов
деструкторов при уничтожении объекта
производного класса?
В каком случае выражение вычислится
быстрее:
В каком случае компилятор выдаст ошибку:
В каком случае описание класса верно?
В каком случае программа выведет строку
на консоль
В каком файле заголовков определён
объект cout:
В программе на языке Си++ обязательно
имеется функция
В сложных выражениях последовательность
выполнения операций определяется:
В чем заключается назначение оператора
перехода goto?
В чем заключается принцип полиморфизма?
В чем заключается суть компоновки
программы?
В чем недостаток использования шаблонов
В чем различие использования следующих
выражений #include <…> и #include «…»
В чем разница между фактическими и
формальными параметрами?
В чём заключаются недостатки при
использовании передачи аргумента в
функцию по значению?
Верен ли код
Вернет ли фукция strcmp(«qwerty»,»QWERTY»);
0?
Возможно ли использование подобного
выражения?
Возможно ли использовать механизм
исключительных ситуаций в деструкторах
Возможно ли использовать механизм исключительных ситуаций в деструкторах
Выберите наиболее правильный вариант
объявления оператора присваивания в
классе A:
Выберите наиболее правильный вариант
объявления оператора сложения и
присваивания двух объектов класса A:
Выберите правильное объявление константы
pi:
Выберите правильное утверждение:
Вызовет ли данный код ошибку компиляции?
Вызовет ли следующее объявление ошибку
компиляции
Двумерный массив Ar представленный в
виде матрицы имеет вид
Для переопределенного оператора верно:
Для получения адреса переменной
используется операция
Для создания исполняемого файла в
системе Unix необходимо
Для того чтобы вывести символ новой
строки, надо:
Для того чтобы выполнить чтение из
файла с произвольной позиции, надо
использовать объект класса
Для чего нужны классы?
Для чего предназначен оператор namespace?
Для чего предназначен фрагмент текста
из заголовочного файла:
Для чего предназначены манипуляторы
потоков ввода-вывода?
Допустима ли следующая конструкция?
Если i = 5, какой будет результат
Если i=3, какой будет результат ?
Если int n=3, какой будет результат ?
Если int n=45, какой будет результат?
Если в арифметическом выражении
участвуют короткое целое и длинное
целое, то:
Если в арифметическом выражении
участвуют целый и вещественный операнды,
то:
Если в классе A определены методы
Если в классе операция delete переопределена
как
Если в классе операция new переопределена
как
Если в классе определяется операция
delete, то
Если в конструкторе класса … произойдет
исключительная ситуация, будет ли
потеряна память при откате по стеку?
Если в массиве A 132 элемента, каким будет
правильное обращение к последнему
элементу массива?
Если в программе объявлен макрос #define
CIRC(x) (3.14159 * (x) * (x)), то как будет подставлен
этот макрос в тексте программы при
следующем вызове:
Если в программе уже имеется функция
с прототипом int func(int k, double f), то какое
из следующих объявлений не вызовет
ошибки компиляции?
Если в производном классе переопределена
операция new то:
Если в процессе компиляции программы
возникла ошибка то:
Если в функции main() выполняется … то
что будет выведено?
Если есть два объявления int qwerty; int
QWERTY; какое из утверждений верно
Если задано #define f(x) x##4 то какой будет
результат после препроцессора ?
Если задано #define foo(x,y) x##y то какой будет
результат после препроцессора ?
Если заданы классы … то что будет
выведено при выполнении оператора
Если записано … то что будет напечатано
в результате выполнения кода?
Если имеется абстрактный класс А и
производный от этого класса класс А1
то какая из записей заведомо неверна?
Если имеется класс с двумя атрибутами
Если имеется код
Если имеется код char a[8]; cin >> a; и
вводится текст «Hello world», то что
будет в массиве a?
Если имеется код double x; cin >> x; и вводится
«12-3», то что будет в переменной x?
Если имеется код int x; cin >> x; и вводится
«1.2», то что будет в переменной x?
Если имеется объявление char
ch1=’a’,ch2=’b’,ch3=’c’; допустима ли запись
ch1=ch2+ch3;
Если имеется объявление float f=4;double d=4;
что будет выведено на экран в результате
выполнения кода
Если имеется объявление int a[15], какой
фрагмент кода выводит все элементы
массива?
Если имеется программа … то какой будет
результат?
Если объявлен тип … сколько байтов
занимает один объект такого типа?
Если определена операция вычитания
для двух объектов класса A, а операция
преобразования к int не определена, что
будет вызвано при
Если
определена операция умножения для двух
объектов класса A и операция преобразования
к int, что будет вызвано при
Если ошибки в алгоритме программы, на
каком этапе они обнаружатся?
Если после выражения стоит точка с
запятой, то
Если указатель объявлен как … какое
из следующих выражений верно:
Если функция вычисления суммы целых
чисел от 1 до n имеет прототип int sum(int n),
запишите определение функции, используя
рекурсию:
Если функция вычисления суммы целых
чисел от 1 до n имеет прототип int sum(int n),
то как будет выглядеть запись определения
функции с использованием рекурсии?
Если функция вычисления суммы целых
чисел, находящихся между двумя заданными
(начальным и конечным), имеет прототип
int sum(int start, int end), запишите определение
функции, используя рекурсию:
Если функция вычисления факториала n
имеет прототип int fact(int n), запишите
определение функции, используя рекурсию:
Если функция имеет тип void, то неверно,
что
Есть ли преимущество выноса определения
функции в отдельный файл
Запись ::func(x) означает, что
Известно, что в классе A определен один
публичный конструктор A(int);. Выберите
из предложенных выражений компилируемые:
Имеется запись: monster — базовый класс,
demon — производный:
Имеется класс:
Имеется объявление char ch1=’A’;. Что будет
выведено на экран при выполнения кода
cout <<ch1+1;
Имеется три объекта класса А: A a1,a2,a3 в
каком выражении возможен неявный вызов
конструктора копирования
Имеется функция
int sum(int a,int a1=2,int a2,int a3,int a4) {return a+a1+a2+a3+a4;}
Имеется функция шаблон … Верен ли код
Исходя из данного кода какое высказывание
верно?
Исходя только из назначения шаблонов
имеет ли смысл делать из данного кода
функцию-шаблон
Как вы понимаете смысл типизированной
переменной? Укажите правильное
высказывание.
Как называется функция, которая вызывает
саму себя?
Какая из записей соответствует обращению
к атрибуту m_arg класса AC в определении
метода этого же класса?
Какая из записей является правильной
записью абстрактного класса?
Какая из перечисленных функций не может
быть конструктором?
Какая операция позволяет получить
значение, записанное по адресу, который
содержится в указателе?
Какая строка данного кода производит
специализацию шаблона?
Какая функция класса, не являясь его
компонентом, имеет доступ к его защищенным
и внутренним компонентам?
Какие бывают конструкторы? (Выберите
ниболее полный ответ)
Какие виды наследования бывают(Выберете
наиболее полный ответ)?
Какие из перечисленных операций не
являются операциями доступа к атрибуту
класса?
Какие из перечисленных типов являются
встроенными типами языка С++?
Какие из следующих выражений являются
константами типа double?
Какие из следующих объявлений метода
func синтаксически правильны?
Какие из следующих символов являются
правильными экранированными
последовательностями?
Какие из следующих утверждений о
копирующем конструкторе правильны?
Какие ключевые слова используются для
создания и обработки исключительных
ситуаций?
Какие операторы не могут быть
переопределены пользователем:
Какие операции используются для контроля
за приведением типов?
Какие операции поддаются перегрузке?
Какие основные области применения
языка Си++?
Какие требования предъявляются к классу
исключительных ситуаций?
Каким будет результат следующей
программы:
Каким будет результат следующей программы:
Каким может быть аргумент деструктора?
Какими по умолчанию объявляются методы
класса?
Какими по умолчанию объявляются элементы
объединения?
Какими по умолчанию объявляются элементы
структуры?
Какова последовательность создания
исполняемого файла:
Каково будет значение переменной k
после выполнения следующего оператора
Каково преимущество использования
ключевого слова const вместо директивы
#define?
Какое выражение верно с точки зрения
целесообразности использования
динамического распределения памяти?
Какое значение будет выведено в
стандартный поток в результате выполнения
следующей программы?
Какое из представленных выражений
выводит на экран
Какое из приведенных выражений верно?
Какое из приведенных имен является
недопустимым в Си++?
Какое из приведенных ниже прототипов
операции сложения для класса
Какое из следующих объявлений является
объявлением неизменяемого указателя?
Какое из следующих определений
представляет собой правильную запись
операции сложения целого числа и
объекта:
Какое из следующих утверждений об
операторе return является верным?
Какое определение функции является
правильным?
Какое правильное объявление виртуальной
функции, которая принимает одно
целочисленное значение и возвращает
void:
Какое приведение типов используется
в следующем выражении?
Какое слово из списка не относится к
зарезервированным словам Си++?
Какой будет результат ?
Какой будет результат выполнения cout
<< a::A::a; в функции main?
Какой будет результат выполнения
следующего кода?
Какой будет результат выполнения следующего кода?
Какой будет результат следующего
выражения?
Какой будет результат следующей
программы?
Какой будет результат у данного
выражения?
Какой длины может быть идентификатор
Какой из наборов перечисляемых значений
записан правильно?
Какой из ниже перечисленных вариантов
не является формой записи вещественного
числа?
Какой из стандартных классов используется
для вывода строк на терминал:
Какой класс используется для вывода
данных во внутреннюю область памяти?
Какой класс может использоваться в
качестве типа атрибута класса?
Какой массив имеет самый большой размер?
Какой правильный вариант описания
шаблона семейства классов?
Какой правильный вариант создания
экземпляра объекта?
Какой правильный вызов функции базового
класса из объекта производного класса,
если в производном классе эта функция
была замещена?
Какой правильный вызов функции базового
класса из объекта производного класса,
если в производном классе эта функция
не была замещена?
Какой правильный заголовок шаблона
Какой результат будет у следующего
выражения?
Какой результат будет у следующего выражения?
Какой результат будет у следующего выражения?
Какой результат вычисления следующего
выражения?
Какой результат следующего выражения
?
Какой результат у следующего выражения?
Какой статус международного стандарта
языка Си++?
Какой
тип будет у следующего выражения ?
Какой тип преобразования типов
используется в следующем выражении?
Класс B наследован от класса A. Отметьте
верное для класса B.
Ключевое слово void обозначает что функция
Комментарий в программе на Си++
Компилятор языка Си++:
Конструктор класса — это метод, который
вызывается при создании объекта для
…(перечислить )
Контекст пространства имен может
содержать:
Могут ли контексты быть вложенными?
Может ли нестатический метод иметь
доступ к статическим методам и атрибутам?
Может ли статический метод класса быть
объявлен как friend?
Можно ли в шаблоне класса определить
статический метод?
Можно ли перегружать оператор разрешения
области видимости -«::»
Можно ли создать объект класса, у
которого все атрибуты и методы –
статические?
На какой строке произойдет ошибка
компиляции?
Найдите недопустимую запись символьной
константы:
Найдите неправильную запись набора
перечисляемых значений:
Нелогической операцией является
Нужно ли учитывать при перегрузке
бинарных операций порядок следования
операндов?
Об ошибке в конструкторе класса может
сигнализировать:
Объявление extern int f; означает:
Оператор throw без аргументов
Операции в выражениях могут быть
Операция «.» обозначает
Операция ++
Определение класса это
Определите
размер структуры
Определите результат выполнения
следующего кода:
Отметьте верное утверждение:
Отметьте все верные утверждения о
статических атрибутах класса:
Отметьте все верные утверждения о
статических методах класса:
Отметьте все неправильные определения
констант:
Отметьте все правильные варианты
продолжения предложения: виртуальный
деструктор
Отметьте все утверждения, которые
считаете верными:
Отметьте допустимые имена функций:
Отметьте истинное высказывание для
данного примера:
Отметьте истинное высказывание, если
вызываются подряд несколько функций:
Отметьте истинное утверждение для
абстрактного класса.
Отметьте истинные высказывания
Отметьте истинные высказывания в
отношении использования макросов и
функций:
Отметьте истинные высказывания в
отношении использования макросов и
функций:
Отметьте истинные высказывания в
отношении потоков, представленные
классом strstream:
Отметьте истинные высказывания:
Отметьте константы в следующем фрагменте
кода:
Отметьте ложные высказывания:
Отметьте ошибочное утверждение:
Отметьте правильное определение
константы:
Отметьте правильное определение
константы:
Отметьте правильные объявления
переменных
Отметьте правильные объявления
переменных:
Отметьте правильный вариант описания
функции шаблона:
Отметьте правильный вариант освобождения
всей памяти, выделенной для трехмерного
массива для следующей программы
Отметьте правильный заголовок шаблона
функции:
Отметьте свойства языка Си++, которые
могут быть источниками возможных ошибок
программирования
Отметьте фрагменты кода, которые можно
назвать выражениями:
Отметьте, какие возможности языка Си++
помогают предупреждать ошибки:
Отметьте, какому определению функции
может соответствовать вызов func(5.98):
Переопределение операции сложения
приведет к(отметьте все правильные
варианты)
После компиляции программы
Правильно ли написан данный код:
Правильный вариант программы, выводящей
«Hello World»:
Представление и диапазоны значений
вещественных чисел соответствуют
стандарту:
При выполнении фрагмента кода … будет
напечатано:
При выходе из функции main
При использовании копирующего
конструктора:
При определении класса-шаблона
При определении метода запись this->
говорит о том, что:
Программа на языке Си++ начинает
выполняться с:
Произойдет ли ошибка компиляции если
функцию
Произойдет ли ошибка при использовании
следующей конструкции:
Произойдет ли ошибка при компиляции
этого кода?
Прототип функции задает
Процесс компиляции программы
Результат работы программы:
С какой целью производится вынесение
функций в отдельный заголовочный файл?
С помошью какой директивы происходит
подключение других модулей программы?
С помощью какого метода можно изменить
текущую позицию в файле?
С помощью механизма friend можно разрешить
обращение к внутренним элементам
класса:
Сколько блоков catch может быть после
блока try?
Сколько вызовов системных функций
всегда будет в программе, независимо
от ее реализации?
Сколько параметров может быть у шаблона
при определении шаблона функции ?
Сколько производных классов можно
получить из базового класса?
Сколько функций может быть в программе
С++?
Совокупность формальных параметров
определяет
Сопоставьте:
Существует ли в С++ готовый набор
шаблонов:
Существует файл «test.dat» в котором
записано «Hello World».Каково будет
содержимое файла после выполнения
кода:
Текст программы можно набирать:
У какой переменой в данном коде самое
длинное «время жизни»?
У какой переменой в данном коде самое
короткое «время жизни»?
Укажите в каких выражениях используются
ключевые слова?
Укажите в каких выражениях неправильно
применяются операции к вещественным
типам
Укажите в каких выражениях переменная
или константа объявляется и ей
присваивается значение:
Укажите в каких строках кода произойдет
ошибка компиляции?
Укажите в каком выражении используется
операция с наивысшим приоритетом?
Укажите в каком выражении правильно
определена переменная в шестнадцатеричной
системе счисления?
Укажите в каком выражении правильно
определена целочисленная переменная?
Укажите в каком выражении правильно
применяются операции к целочисленным
типам
Укажите в каком выражении произойдет
потеря точности
Укажите все ключевые слова в приведенном
примере?
Укажите какие компиляторы языка Си++
могут быть использованы в системе Unix.
Укажите каким будет результат вычисления
цикла?
Укажите какое из выражений будет
правильным?
Укажите какой будет результат вычисления
k?
Укажите какой результат будет у
следующего примера?
Укажите какому классу принадлежит
атрибут Z1
Укажите неправильный идентификатор:
Укажите основные используемые
манипуляторы потоков.
Укажите правильное использование
оператора friend
Укажите правильное объявление функции
Укажите правильное объявление шаблона
функции, если в программе производится
вызов double х = zero<double>();
Укажите правильное объявление?
Укажите правильные присваивания
значений переменным и константам
Укажите правильный доступ к членам
класса:
Укажите правильный идентификатор для
имени переменной:
Укажите, где происходит объявление
констант или переменных
Файл имеющий имя «test_file.cpp» это:
Функция вычисляет произведение двух
чисел. Исходные данные вводятся с
клавиатуры. Какие проверки целесообразно
ввести в программе?
Функция объявлена как friend класса.
Отметьте верное.
Чему будет равен результат вычисления
выражения: float A = 2, B = 20, C; C = (B = A = 5) + 1;
Чему будет равен результат вычисления
выражения: int d=5; bool b = true, c; c = (!b||(d>3));
Чему будет равен результат вычисления:
Чему будет равна переменная k в результате
вычисления цикла?
Чему равен результат вычисления
выражения
Чему равен результат вычисления выражения
Чему равен результат вычисления выражения
Чему равно значение выражения !((1 || 0)
&& 0) ?
Чему равно значение выражения 54 <<
3 ?
Чему равно значение целой переменной
при вычислении выражения 21/5*3?
Что будет в результате выполнения
следующей программы?
Что будет выведено в результате
Что будет выведено в результате
выполнения данного кода?
Что будет выведено в результате
выполнения следующего кода?
Что будет выведено в стандартный поток
в результате выполнения программы
Что будет выведено в стандартный поток
вывода в результате исполнения следущей
программы?
Что будет выведено на экран в результате
выполнения данного кода?
Что будет выведено на экран в результате
выполнения кода?
Что будет выведено на экран в результате выполнения кода?
Что будет выведено на экран в результате
выполнения приведенной ниже программы:
Что будет выведено на экран в результате выполнения приведенной ниже программы?
Что
будет выведено на экран в результате
следующего выражения?
Что
будет выведено на экран после выполнения
программы?
Что будет выведено на экран, если вызвать
данную функцию последовательно три
раза?
Что будет делать функция
find(arr+2,arr+ARR_SIZE,5)?
Что будет на экране после выполнения
данного кода
Что будет на экране после выполнения
программы
Что будет напечатано в результате
выполнения следующего кода?
Что выведет программа в стандартный
поток вывода?
Что выведет следующая программа ?
Что выполняет операция «delete [] v;»
в данном ниже коде:
Что выполняется в первую очередь при
компоновке программы?
Что вычисляет эта функция:
Что из себя представляет динамическое
выделение памяти?
Что может быть аргументом оператора
throw?
Что нужно сделать для освобождения
памяти после выполнения такого кода ?
Что означает cout << flush ?
Что означает cout << setw(3) ?
Что означает запись for (;;)?
Что означает запись while (false);?
Что описывает данная строка программы:
float mas=new int[3][2]?
Что описывает данный программный код?
Что понимается под потоком в языке C++
Что произойдет после объявления в
программе данного набора перечисляемых
значений: enum{N=0, E=1, S=2, W=3};?
Что произойдет после следующего примера?
Что произойдет при выводе в файл,
открытый с помощью
Что произойдет при выполнении ?
Что произойдет, если определение функции
будет находиться в файле в двух местах?
Что произойдёт если операция выделения
памяти new завершится неудачно?
Что произойдёт при использовании
неправильного адреса в операции delete?
Что происходит при попытке выполнить
оператор return внутри блока catch?
Что содержится в записи минимального
по своим возможностям класса?
Что такое cout?
Что целесообразно определять в public
разделе класса?
Что является минимальной областью
видимости имен?
Что является результатом компоновки
программы?
Шаблон A и его специализации объявлены
следующим образом:
Имя метода, также как и переменной:
должно начинаться только с буквы;может состоять из любой последовательности строчных и прописных букв, но принято использовать только латинские;может содержать символы подчеркивания (_) и знака доллара ($);должно учитывать регистр (например, имена MyMethod и myMethod компилятор посчитает разными именами);не должно совпадать с именами операторов и зарезервированных слов самого языка Java;не должно совпадать с именами clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait. Если только вы не хотите явно переопределить действия этих методов.
Методы clone(), equals(), finalize(), getClass(), hashCode(), notify(), notifyAll(), toString(), wait() объявлены в классе Object, главном класса Java, от которого наследуются все другие классы. Поэтому, даже если ваш класс явно не наследуется от другого класса, неявно он является подклассом Object, а значит имеет все эти методы в любом случае.
Добавлено через 16 секунд
У нас вот что только написано
Теги: Си переменные. char, int, unsigned, long, long long, float, double, long double, long float, lexical scoping. Объявление переменных. Область видимости. Инициализация переменных. Имена переменных. Экспоненциальная форма.
Переменные
Переменные используются для хранения значений (sic!).
Переменная характеризуется типом и именем. Начнём с имени. В си переменная может начинаться с подчерка или буквы, но не с числа. Переменная может включать в себя символы английского алфавита, цифры и знак подчёркивания. Переменная не должна совпадать с ключевыми словами (это специальные слова, которые используются в качестве управляющих конструкций, для определения типов и т.п.)
| auto | double | int | struct |
| break | else | long | switch |
| register | typedef | char | extern |
| return | void | case | float |
| unsigned | default | for | signed |
| union | do | if | sizeof |
| volatile | continue | enum | short |
| while | inline |
А также ряд других слов, специфичных для данной версии компилятора, например far, near, tiny, huge, asm, asm_ и пр.
Например, правильные идентификаторы
a, _, _1_, Sarkasm, a_long_variable, aLongVariable, var19, defaultX, char_type
неверные
1a, $value, a-long-value, short
Си — регистрозависимый язык. Переменные с именами a и A, или end и END, или perfectDark и PerfectDarK – это различные переменные.
Типы переменных
Тип переменной определяет
- 1) Размер переменной в байтах (сколько байт памяти выделит компьютер для хранения значения)
- 2) Представление переменной в памяти (как в двоичном виде будут расположены биты в выделенной области памяти).
В си несколько основных типов. Разделим их на две группы — целые и числа с плавающей точкой.
Целые
- char — размер 1 байт. Всегда! Это нужно запомнить.
- short — размер 2 байта
- int — размер 4 байта
- long — размер 4 байта
- long long — размер 8 байт.
Здесь следует сделать замечание. Размер переменных в си не определён явно, как размер в байтах. В стандарте только указано, что
char <= short <= int <= long <= long long
Указанные выше значения характерны для компилятора VC2012 на 32-разрядной машине. Так что, если ваша программа зависит от размера переменной, не поленитесь узнать её размер.
Теперь давайте определим максимальное и минимальное число, которое может хранить переменная каждого из типов. Числа могут быть как положительными, так и отрицательными. Отрицательные числа используют один бит для хранения знака. Иногда знак необходим (например, храним счёт в банке, температуру, координату и т.д.), а иногда в нём нет необходимости (вес, размер массива, возраст человека и т.д.). Для этого в си используется модификатор типа signed и unsigned.
unsigned char — все 8 бит под число, итого имеем набор чисел от 00000000 до 11111111 в двоичном виде, то есть от 0 до 255
signed char от -128 до 128.
В си переменные по умолчанию со знаком. Поэтому запись char и signed char эквивалентны.
| Тип | Размер, байт | Минимальное значение | Максимальное значение |
|---|---|---|---|
| unsigned char | 1 | 0 | 255 |
| signed char ( char ) |
1 | -128 | 127 |
| unsigned short | 2 | 0 | 65535 |
| signed short ( short ) |
2 | -32768 | 32767 |
| unsigned int ( unsigned ) |
4 | 0 | 4294967296 |
| signed int ( int ) |
4 | -2147483648 | 2147483647 |
| unsigned long | 4 | 0 | 4294967296 |
| signed long ( long ) |
4 | -2147483648 | 2147483647 |
| unsigned long long | 8 | 0 | 18446744073709551615 |
| signed long long ( long long ) |
8 | -9223372036854775808 | 9223372036854775807 |
sizeof
В си есть оператор, который позволяет получить размер переменной в байтах.
sizeof переменная, или sizeof(переменная) или sizeof(тип). Это именно оператор, потому что функция не имеет возможности получить информацию о размере типов
во время выполнения приложения.
Напишем небольшую программу чтобы удостовериться в размерах переменных.
#include<conio.h>
#include<stdio.h>
int main() {
char c;
short s;
int i;
long l;
long long L;
//Вызов sizeof как "функции"
printf("sizeof(char) = %dn", sizeof(c));
printf("sizeof(short) = %dn", sizeof(s));
printf("sizeof(int) = %dn", sizeof(i));
printf("sizeof(long) = %dn", sizeof(l));
printf("sizeof(long long) = %dn", sizeof(L));
//Вызов как оператора
printf("sizeof(char) = %dn", sizeof c);
printf("sizeof(short) = %dn", sizeof s);
printf("sizeof(int) = %dn", sizeof i);
printf("sizeof(long) = %dn", sizeof l);
printf("sizeof(long long) = %dn", sizeof L);
_getch();
}
(Я думаю ясно, что переменные могут иметь любое валидное имя). Эту программу можно было написать и проще
#include<conio.h>
#include<stdio.h>
int main() {
printf("sizeof(char) = %dn", sizeof(char));
printf("sizeof(short) = %dn", sizeof(short));
printf("sizeof(int) = %dn", sizeof(int));
printf("sizeof(long) = %dn", sizeof(long));
printf("sizeof(long long) = %dn", sizeof(long long));
//нельзя произвести вызов sizeof как оператора для имени типа
//sizeof int - ошибка компиляции
_getch();
}
В си один и тот же тип может иметь несколько названий
short === short int
long === long int
long long === long long int
unsigned int === unsigned
Типы с плавающей точкой
- float — 4 байта,
- long float — 8 байт
- double — 8 байт
- long double — 8 байт.
Здесь также приведены значения для VC2012, по стандарту размер типов
float <= long float <= double <= long double
все числа с плавающей точкой — со знаком.
| Тип | Размер, байт | Количество значащих знаков мантиссы | Минимальное значение | Максимальное значение |
|---|---|---|---|---|
| float | 4 | 6-7 | 1.175494351 E – 38 | 3.402823466 E + 38 |
| double | 8 | 15-16 | 2.2250738585072014 E – 308 | 1.7976931348623158 E + 308 |
Переполнение переменных
Си не следит за переполнением переменных. Это значит, что постоянно увеличивая значение, скажем, переменной типа int в конце концов мы «сбросим значение»
#include <conio.h>
#include <stdio.h>
void main() {
unsigned a = 4294967295;
int b = 2147483647;
//Переполнение беззнакового типа
printf("%un", a);
a += 1;
printf("%u", a);
//Переполнение знакового типа
printf("%dn", b);
b += 1;
printf("%d", b);
getch();
}
Вообще, поведение при переполнении переменной определено только для типа unsigned: Беззнаковое целое сбросит значение.
Для остальных типов может произойти что угодно, и если вам необходимо следить за переполнением, делайте это вручную, проверяя аргументы,
либо используйте иные способы, зависящие от компилятора и архитектуры процессора.
Постфиксное обозначение типа
При работе с числами можно с помощью литер в конце числа явно указывать его тип, например
- 11 — число типа int
- 10u — unsigned
- 22l или 22L — long
- 3890ll или 3890LL — long long (а также lL или Ll)
- 80.0f или 80.f или 80.0F — float (обязательно наличие десятичной точки в записи)
- 3.0 — число типа double
Экспоненциальная форма записи также по умолчанию обозначает число типа double.
#include<conio.h>
#include<stdio.h>
int main() {
printf("sizeof(int) = %dn", sizeof(10));
printf("sizeof(unigned) = %dn", sizeof(10u));
printf("sizeof(long) = %dn", sizeof(10l));
printf("sizeof(long long) = %dn", sizeof(10ll));
printf("sizeof(float) = %dn", sizeof(10.f));
printf("sizeof(double) = %dn", sizeof(10.));
printf("sizeof(double) = %dn", sizeof(10e2));
getch();
}
Следующий код, однако, не будет приводить к ошибкам, потому что происходит неявное преобразование типа
int a = 10u; double g = 3.f;
Шестнадцатеричный и восьмеричный формат
Во время работы с числами можно использовать шестнадцатеричный и восьмеричный формат представления.
Числа в шестнадцатиричной системе счисления начинаются с 0x, в восьмеричной системе с нуля. Соответственно, если число начинается
с нуля, то в нём не должно быть цифр выше 7:
#include<conio.h>
#include<stdio.h>
void main() {
int x = 0xFF;
int y = 077;
printf("hex x = %xn", x);
printf("dec x = %dn", x);
printf("oct x = %on", x);
printf("oct y = %on", y);
printf("dec y = %dn", y);
printf("hex y = %x", y);
getch();
}
Экспоненциальная форма представления чисел
Экспоненциальной формой представления числа называют представление числа в виде
M
e
±
p
, где M — мантиса числа, p — степень десяти. При этом у мантисы должен быть один ненулевой знак перед десятичной запятой.
Например 1.25 === 1.25e0, 123.5 === 1.235e2, 0.0002341 === 2.341e-4 и т.д.
Представления 3.2435e7 эквивалентно 3.2435e+7
Существеут и другое представление («инженерное»), в котором степень должна быть кратной тройке.
Например 1.25 === 1.25e0, 123.5 === 123.5e0, 0.0002341 === 234.1e-6, 0.25873256 === 258.73256e-3 и т.д.
Объявление переменных
В си переменные объявляются всегда в начале блока (блок — участок кода ,ограниченный фигурными скобками)
<возвращаемый тип> <имя функции> (<тип> <аргумент>[, <тип> <аргумент>]) {
объявление переменных
всё остальное
}
При объявлении переменной пишется её тип и имя.
int a; double parameter;
Можно объявить несколько переменных одного типа, разделив имена запятой
long long arg1, arg2, arg3;
Например
#include <stdio.h>
#include <conio.h>
int main() {
int a = 10;
int b;
while (a>0){
int z = a*a;
b += z;
}
}
Здесь объявлены переменные a и b внутри функции main, и переменная z внутри тела цикла.
Следующий код вызовет ошибку компиляции
int main() {
int i;
i = 10;
int j;
}
Это связано с тем, что объявление переменной стоит после оператора присваивания.
При объявлении переменных можно их сразу инициализировать.
int i = 0;
При этом инициализация при объявлении переменной не считается за отдельный оператор, поэтому следующий код будет работать
int main() {
int i = 10;
int j;
}
Начальное значение переменной
Очень важно запомнить, что переменные в си не инициализируются по умолчанию нулями, как во многих других языках программирования.
После объявления переменной в ней хранится «мусор» — случайное значение, которое осталось в той области памяти, которая была выделена под переменную.
Это связано, в первую очередь, с оптимизацией работы программы: если нет необходимости в инициализации, то незачем тратить ресурсы
для записи нулей (замечание: глобальные переменные инициализируются нулями, почему так, читайте в этой статье).
#include<conio.h>
#include<stdio.h>
int main() {
int i;
printf("%d", i);
getch();
}
Если выполнять эту программу на VC, то во время выполнения вылетит предупреждение
Run-Time Check Failure #3 — The variable ‘i’ is being used without being initialized.
Если нажать «Продолжить», то программа выведет «мусор». В многих других компиляторах при выполнении программы не будет предупреждения.
Область видимости переменной
Переменные бывают локальными (объявленными внутри какой-нибудь функции) и глобальными. Глобальная переменная видна всем функциям, объявленным в данном файле.
Локальная переменная ограничена своей областью видимости.
Когда я говорю, что переменная «видна в каком-то месте», это означает, что в этом месте она определена и её можно использовать.
Например, рассмотрим программу, в которой есть глобальная переменная
#include<conio.h>
#include<stdio.h>
int global = 100;
void foo() {
printf("foo: %dn", global);
}
void bar(int global) {
printf("bar: %dn", global);
}
int main() {
foo();
bar(333);
getch();
}
Будет выведено
foo: 100
bar: 333
Здесь глобальная переменная global видна всем функциям. Но аргумент функции затирает глобальную переменную, поэтому при передаче аргумента 333 выводится локальное значение 333.
Вот другой пример
#include<conio.h>
#include<stdio.h>
int global = 100;
int main() {
int global = 555;
printf("%dn", global);
getch();
}
Программа выведет 555. Также, как и в прошлом случае, локальная переменная «важнее».
Переменная, объявленная в некоторой области видимости не видна вне её, например
#include<conio.h>
#include<stdio.h>
int global = 100;
int main() {
int x = 10;
{
int y = 30;
printf("%d", x);
}
printf("%d", y);
}
Этот пример не скомпилируется, потому что переменная y существует только внутри своего блока.
Вот ещё пример, когда переменные, объявленные внутри блока перекрывают друг друга
#include<conio.h>
#include<stdio.h>
int global = 100;
int main() {
int x = 10;
{
int x = 20;
{
int x = 30;
printf("%dn", x);
}
printf("%dn", x);
}
printf("%dn", x);
getch();
}
Программа выведет
30
20
10
Глобальных переменных необходимо избегать. Очень часто можно услышать такое. Давайте попытаемся разобраться, почему.
В ваших простых проектах глобальные переменные выглядят вполне нормально. Но представьте, что у вас приложение, которое
- 1) Разрабатывается несколькими людьми и состоит из сотен тысяч строк кода
- 2) Работает в несколько потоков
Во-первых, глобальная переменная, если она видна всем, может быть изменена любой частью программы.
Вы изменили глобальную переменную, хотите её записать, а другая часть программы уже перезаписала в неё другое значение
(на самом деле это целый класс проблем, которые возникают в многопоточной среде). Во-вторых, при больших размерах проекта не уследить,
кто и когда насоздавал глобальных переменных. В приведённых выше примерах видно, как переменные могут перекрывать друг друга, то же произойдёт и в крупном проекте.
Безусловно, есть ситуации, когда глобальные переменные упрощают программу, но такие ситуации случаются не часто и не в ваших домашних заданиях, так что НЕ СОЗДАВАЙТЕ ГЛОБАЛЬНЫХ ПЕРЕМЕННЫХ!
Переменные могут быть не только целочисленными и с плавающей точкой. Существует множество других типов, которые мы будем изучать в дальнейшем.
Q&A
Всё ещё не понятно? – пиши вопросы на ящик
Пишем первую программу (Linux)
Добавлено 2 мая 2021 в 15:36
Константные (постоянные) переменные
До сих пор все переменные, которые мы видели, были непостоянными, то есть их значения можно изменить в любое время. Например:
int x { 4 }; // инициализируем x значением 4
x = 5; // меняем значение x на 5
Однако иногда бывает полезно определять переменные со значениями, которые нельзя изменить. Например, рассмотрим ускорение свободного падения у поверхности Земли: 9,8 м/с2. Маловероятно, что в ближайшее время оно изменится (а если это произойдет, у вас, вероятно, возникнут более серьезные проблемы, чем изучение C++). Определение этого значения как константы помогает гарантировать, что оно не будет случайно изменено.
Чтобы сделать переменную константой, просто поместите ключевое слово const до или после типа переменной, например:
const double gravity { 9.8 }; // предпочтительное использование const перед типом
int const sidesInSquare { 4 }; // хорошо, но не рекомендуется
Хотя C++ принимает const до или после типа, мы рекомендуем использовать константу перед типом, потому что это лучше соответствует соглашению обычного английского языка, согласно которому модификаторы ставятся перед изменяемым объектом (например, «green ball» (зеленый шар), а не «ball green» (шар зеленый)).
Константные переменные должны быть инициализированы, когда вы их определяете, после этого это значение не может быть изменено с помощью присваивания.
Объявление переменной как const предотвращает непреднамеренное изменение ее значения:
const double gravity { 9.8 };
gravity = 9.9; // не допускается, это вызовет ошибку компиляции
Определение константной переменной без ее инициализации также вызовет ошибку компиляции:
const double gravity; // ошибка компиляции, должна быть инициализирована при определении
Обратите внимание, что константные переменные могут быть инициализированы из других переменных (включая неконстантные):
std::cout << "Enter your age: ";
int age{};
std::cin >> age;
const int usersAge { age }; // usersAge изменить нельзя
const часто используется с параметрами функции:
void printInteger(const int myValue)
{
std::cout << myValue;
}
Задание параметра функции константой делает две вещи. Во-первых, это сообщает человеку, вызывающему функцию, что функция не изменит значение myValue. Во-вторых, это гарантирует, что функция не изменит значение myValue.
Когда аргументы передаются по значению, нас обычно не волнует, изменяет ли функция значение параметра (поскольку это всего лишь копия, которая в любом случае будет уничтожена в конце функции). По этой причине мы обычно не делаем константными параметры, передаваемые по значению. Но позже мы поговорим о других типах параметров функций (где изменение значения параметра приведет к изменению значения переданного аргумента). Для этих типов параметров важно разумное использование const.
Константы времени выполнения и константы времени компиляции
На самом деле C++ имеет два разных типа констант.
Константы времени выполнения – это те, значения инициализации которых могут быть вычислены только во время выполнения (когда ваша программа работает). Такие переменные, как usersAge и myValue в приведенных выше фрагментах, являются константами времени выполнения, поскольку компилятор не может определить их начальные значения во время компиляции. usersAge полагается на ввод данных пользователем (который может быть предоставлен только во время выполнения), а myValue зависит от значения, переданного в функцию (которое известно только во время выполнения). Однако после инициализации значение этих констант изменить нельзя.
Константы времени компиляции – это те, чьи значения инициализации могут быть вычислены во время компиляции (когда ваша программа компилируется). Переменная gravity выше является примером постоянной времени компиляции. Константы времени компиляции позволяют компилятору выполнять оптимизацию, недоступную для констант времени выполнения. Например, всякий раз, когда используется gravity, компилятор может просто заменить идентификатор gravity литералом 9.8 типа double.
Когда вы объявляете константную переменную, компилятор неявно отслеживает, является ли она константой времени выполнения или константой времени компиляции.
В большинстве случаев это не имеет значения, но есть несколько странных случаев, когда C++ требует константу времени компиляции вместо константы времени выполнения, например, при создании экземпляра типа – о чем мы поговорим позже.
constexpr
Чтобы обеспечить большую конкретность, в C++11 введено ключевое слово constexpr, которое гарантирует, что константа должна быть константой времени компиляции:
constexpr double gravity { 9.8 }; // хорошо, значение 9,8 может быть определено во время компиляции
constexpr int sum { 4 + 5 }; // хорошо, значение 4 + 5 может быть определено во время компиляции
std::cout << "Enter your age: ";
int age{};
std::cin >> age;
constexpr int myAge { age }; // плохо, age не может быть определен во время компиляции
Переменные constexpr являются константными. Это станет важным, когда мы поговорим о других влияниях const в следующих уроках.
Лучшая практика
Любую переменную, которую нельзя изменять после инициализации, и инициализатор которой известен во время компиляции, следует объявлять как constexpr.
Любую переменную, которую нельзя изменять после инициализации, и инициализатор которой неизвестен во время компиляции, следует объявлять как const.
Именование ваших константных переменных
Некоторые программисты для константных переменных предпочитают использовать имена полностью из заглавных букв. Другие используют обычные имена переменных с префиксом ‘k‘. Однако мы будем использовать обычные соглашения об именах переменных, которые встречаются чаще. Константные переменные действуют точно так же, как обычные переменные во всех случаях, за исключением того, что им не может быть присвоено другое значение, поэтому нет особой причины, по которой они должны обозначаться как-то по-особенному.
Символьные константы
В предыдущем уроке «4.13 – Литералы» мы обсуждали «магические числа», которые представляют собой литералы, используемые в программе для представления постоянного значения. Что делать, если магические числа – это плохо? Ответ: используйте символические константы! Символьная константа – это имя, данное константному литеральному значению. В C++ есть два способа объявить символьную константу. Один из них хороший, а один нет. Мы покажем вам оба.
Плохое решение: использование объекто-подобных макросов с параметром подстановки в качестве символьных констант
Сначала мы покажем вам менее желательный способ определения символьной константы. Этот метод обычно использовался во многих старых кодах, поэтому вы всё еще можете его увидеть.
В уроке «2.9 – Знакомство с препроцессором» вы узнали, что у объекто-подобных макросов есть две формы: одна не принимает параметр подстановки (обычно используется для условной компиляции), а другая имеет параметр подстановки. Здесь мы поговорим о случае с параметром подстановки. Он имеет следующую форму:
#define идентификатор подставляемый_текст
Каждый раз, когда препроцессор встречает эту директиву, любое дальнейшее появление идентификатора заменяется на подставляемый_текст. Идентификатор традиционно набирается заглавными буквами с использованием подчеркивания для обозначения пробелов.
Рассмотрим следующий фрагмент:
#define MAX_STUDENTS_PER_CLASS 30
int max_students { numClassrooms * MAX_STUDENTS_PER_CLASS };
Когда вы компилируете свой код, препроцессор заменяет все экземпляры MAX_STUDENTS_PER_CLASS литеральным значением 30, которое затем компилируется в ваш исполняемый файл.
Вы, вероятно, согласитесь, что это по нескольким причинам гораздо более интуитивно понятно, чем использование магического числа. MAX_STUDENTS_PER_CLASS даже без комментария предоставляет контекст для того, что программа пытается сделать. Во-вторых, если количество студентов в классе изменяется, нам нужно изменить значение MAX_STUDENTS_PER_CLASS только в одном месте, и все экземпляры MAX_STUDENTS_PER_CLASS при следующей компиляции будут заменены новым литеральным значением.
Рассмотрим наш второй пример с использованием символьных констант #define:
#define MAX_STUDENTS_PER_CLASS 30
#define MAX_NAME_LENGTH 30
int max_students { numClassrooms * MAX_STUDENTS_PER_CLASS };
setMax(MAX_NAME_LENGTH);
В этом случае очевидно, что MAX_STUDENTS_PER_CLASS и MAX_NAME_LENGTH должны быть независимыми, даже если они имеют одно и то же значение (30). Таким образом, если нам нужно обновить размер класса, мы не сможем случайно изменить длину имени.
Так почему бы не использовать #define для создания символьных констант? Есть (по крайней мере) три основных проблемы.
Во-первых, поскольку макросы вычисляются препроцессором, который заменяет символьное имя определенным значением, символьные константы, определенные через #define, не отображаются в отладчике (который показывает ваш фактический код). Таким образом, хотя компилятор будет компилировать int max_students {numClassrooms * 30};, в редакторе кода вы увидите int max_students {numClassrooms * MAX_STUDENTS_PER_CLASS};, и MAX_STUDENTS_PER_CLASS не будет отслеживаться в отладчике. Вам нужно будет найти определение MAX_STUDENTS_PER_CLASS, чтобы узнать его фактическое значение. Это может затруднить отладку ваших программ.
Во-вторых, макросы могут конфликтовать с обычным кодом. Например:
#include "someheader.h"
#include <iostream>
int main()
{
int beta { 5 };
std::cout << beta;
return 0;
}
Если в someheader.h появляется определение с помощью #define макроса с именем beta, эта простая программа сломается, так как препроцессор заменит имя целочисленной переменной beta на какое-то значение макроса.
В-третьих, макросы не подчиняются обычным правилам области видимости, что означает, что в редких случаях макрос, определенный в одной части программы, может конфликтовать с кодом, написанным в другой части программы, с которой он не должен был взаимодействовать.
Предупреждение
Избегайте использования #define для создания макросов символьных констант.
Лучшее решение: используйте переменные constexpr
Лучший способ создать символьные константы – использовать переменные constexpr:
constexpr int maxStudentsPerClass { 30 };
constexpr int maxNameLength { 30 };
Поскольку это обычные переменные, они доступны для отслеживания в отладчике, имеют обычную область видимости и позволяют избежать других странных форм поведения.
Лучшая практика
Используйте переменные constexpr, чтобы указать имя и контекст для ваших магических чисел.
Использование символьных констант в программе с несколькими исходными файлами
Во многих приложениях заданная символьная константа должна использоваться во всем коде (а не только в одном месте). Сюда могут входить неизменяемые физические или математические константы (например, число Пи или число Авогадро) или значения «настройки» для конкретного приложения (например, коэффициенты трения или силы тяжести). Вместо того чтобы переопределять их каждый раз, когда они необходимы, лучше объявить их один раз в центре и использовать везде, где это необходимо. Таким образом, если вам когда-нибудь понадобится изменить их, вам нужно будет изменить их только в одном месте.
В C++ есть несколько способов облегчить это, но, вероятно, самый простой из них будет следующим:
- создать заголовочный файл для хранения этих констант;
- внутри этого заголовочного файла объявить пространство имен (мы поговорим об этом подробнее в уроке «6.2 – Пользовательские пространства имен»);
- добавить все свои константы в это пространство имен (убедитесь, что они
constexprв C++11/14 илиinline constexprв C++17 или новее); - включить с помощью
#includeэтот заголовочный файл везде, где он вам нужен.
Например:
constants.h (C++11/14):
#ifndef CONSTANTS_H
#define CONSTANTS_H
// определяем собственное пространство имен для хранения констант
namespace constants
{
constexpr double pi { 3.14159 };
constexpr double avogadro { 6.0221413e23 };
constexpr double my_gravity { 9.2 }; // m/s^2 - ускорение свободного падения
// на этой планете меньше
// ... другие связанные константы
}
#endif
В C++17 лучше использовать inline constexpr:
constants.h (C++17 или новее):
#ifndef CONSTANTS_H
#define CONSTANTS_H
// определяем собственное пространство имен для хранения констант
namespace constants
{
inline constexpr double pi { 3.14159 }; // inline constexpr - только для C++17 или новее
inline constexpr double avogadro { 6.0221413e23 };
inline constexpr double my_gravity { 9.2 }; // m/s^2 - ускорение свободного падения
// на этой планете меньше
// ... другие связанные константы
}
#endif
Для доступа к этим константам в файлах .cpp используйте оператор разрешения области видимости (::):
main.cpp:
#include "constants.h"
#include <iostream>
int main()
{
std::cout << "Enter a radius: ";
int radius{};
std::cin >> radius;
double circumference { 2.0 * radius * constants::pi };
std::cout << "The circumference is: " << circumference << 'n';
return 0;
}
Если у вас есть и физические константы, и значения настроек для каждого приложения, вы можете выбрать использование двух наборов файлов: один для физических значений, которые никогда не изменятся, а другой для специфичных значений настроек отдельно для каждой программы. Таким образом, вы можете повторно использовать физические значения в любой программе.
Теги
C++ / CppconstconstexprLearnCppДля начинающихКонстантаОбучениеПрепроцессорПрограммированиеСимвольная константа
Процедурно-ориентированное программирование
В части II были представлены базовые компоненты языка С++: встроенные типы
данных (int и double), типы классов (string и vector) и операции, которые можно
совершать над данными. В части III мы увидим, как из этих компонентов строятся
функции, служащие для реализации алгоритмов.
В каждой программе на С++ должна присутствовать функция main(), которая получает
управление при запуске программы. Все остальные функции, необходимые для решения
задачи, вызываются из main(). Они обмениваются информацией при помощи параметров,
которые получают при вызове, и возвращаемых значений. В главе 7 представлен
соответствующие механизмы С++.
Функции используются для того, чтобы организовать программу в виде совокупности
небольших и не зависящих друг от друга частей. Она инкапсулирует алгоритм или
набор алгоритмов, применяемых к некоторому набору данных. Объекты и типы можно
определить так, что они будут использоваться в течение всего времени работы
программы. Однако, если некоторые объекты или типы применяются только в части
программы, предпочтительнее ограничить область их использования именно этой
частью и объявить внутри той функции, где они нужны. Понятие видимости предоставляет
в распоряжение программиста механизм, позволяющий ограничивать область применения
объектов. Различные области видимости, поддерживаемые языком С++, мы рассмотрим
в главе 8.
Для облегчения использования функций С++ предлагает множество средств, рассматриваемых
нами в части III. Первым из них является перегрузка. Функции, которые выполняют
семантически одну и ту же операцию, но работают с разными типами данных и потому
имеют несколько отличающиеся реализации, могут иметь общее имя. Например, все
функции для печати значений разных типов, таких, как int, string и т.д., называются
print(). Поскольку программисту не приходится запоминать много разных имен для
одной и той же операции, пользоваться ими становится проще. Компилятор сам подставляет
нужное в зависимости от типов фактических аргументов. В главе 9 объясняется,
как объявлять и использовать перегруженные функции и как компилятор выбирает
подходящую из набора перегруженных.
Вторым средством, облегчающим использование функций, является механизм шаблонов.
Шаблон — это обобщенное определение, которое используется для конкретизации
— автоматической генерации потенциально бесконечного множества функций, различающихся
только типами входных данных, но не действиями над ними. Этот механизм описывается
в главе 10.
Функции обмениваются информацией с помощью значений, которые они получают при
вызове (параметров), и значений, которые они возвращают. Однако этот механизм
может оказаться недостаточным при возникновении непредвиденной ситуации в работе
программы. Такие ситуации называются исключениями, и, поскольку они требуют
немедленной реакции, необходимо иметь возможность послать сообщение вызывающей
программе. Язык С++ предлагает механизм обработки исключений, который позволяет
функциям общаться между собой в таких условиях. Этот механизм рассматривается
в главе 11.
Наконец, стандартная библиотека предоставляет нам обширный набор часто используемых
функций — обобщенных алгоритмов. В главе 12 описываются эти алгоритмы и способы
их использования с контейнерными типами из главы 6 и со встроенными массивами.
Мы рассмотрели, как объявлять переменные (глава 3),
как писать выражения (глава 4) и инструкции (глава
5). Здесь мы покажем, как группировать эти компоненты в определения функций,
чтобы облегчить их многократное использование внутри программы. Мы увидим, как
объявлять и определять функции и как вызывать их, рассмотрим различные виды
передаваемых параметров и обсудим особенности использования каждого вида. Мы
расскажем также о различных видах значений, которые может вернуть функция. Будут
представлены четыре специальных случая применения функций: встроенные (inline),
рекурсивные, написанные на других языках и объявленные директивами связывания,
а также функция main(). В завершение главы мы разберем более сложное понятие
– указатель на функцию.
7.1. Введение
Функцию можно рассматривать как операцию, определенную пользователем. В общем
случае она задается своим именем. Операнды функции, или формальные параметры,
задаются в списке параметров, через запятую. Такой список заключается
в круглые скобки. Результатом функции может быть значение, которое называют
возвращаемым. Об отсутствии возвращаемого значения сообщают ключевым
словом void. Действия, которые производит функция, составляют ее тело;
оно заключено в фигурные скобки. Тип возвращаемого значения, ее имя, список
параметров и тело составляют определение функции. Вот несколько примеров:
inline int abs( int obj )
{
// возвращает абсолютное значение iobj
return( iobj < 0 ? -iobj : iobj );
}
inline int min( int p1, int p2 )
{
// возвращает меньшую из двух величин
return( pi < p2 ? pi : p2 );
}
int gcd( int vl, int v2 )
{
// возвращает наибольший общий делитель
while ( v2 )
{
int temp = v2;
v2 = vl % v2;
vl = temp;
}
return vl;
}
Выполнение функции происходит тогда, когда в тексте программы встречается оператор
вызова. Если функция принимает параметры, при ее вызове должны быть указаны
фактические параметры, аргументы. Их перечисляют внутри скобок, через запятую.
В следующем примере main() дважды вызывает abs() и по одному разу min() и gcd().
Функция main() определяется в файле main.C.
#include
int main()
{
// прочитать значения из стандартного ввода
cout << "Введите первое значение: ";
int i;
cin >> i;
if ( !cin ) {
cerr << "!? Ошибка ввода - аварийный выход!n";
return -1;
}
cout << "Введите второе значение: ";
int j;
cin >> j;
if ( !cin ) {
cerr << "!? Ошибка ввода - аварийный выход!n";
return -2;
}
cout << "nmin: " << min( i, j ) << endl;
i = abs( i );
j = abs( j );
cout << "НОД: " << gcd( i, j ) << endl;
return 0;
}
Вызов функции может обрабатываться двумя разными способами. Если она объявлена
встроенной (inline), то компилятор подставляет в точку вызова ее тело. Во всех
остальных случаях происходит нормальный вызов, который приводит к передаче управления
ей, а активный в этот момент процесс на время приостанавливается. По завершении
работы выполнение программы продолжается с точки, непосредственно следующей
за точкой вызова. Работа функции завершается выполнением последней инструкции
ее тела или специальной инструкции return.
Функция должна быть объявлена до момента ее вызова, попытка использовать необъявленное
имя приводит к ошибке компиляции. Определение функции может служить ее объявлением,
но ему разрешено появиться в программе только один раз. Поэтому обычно его помещают
в отдельный исходный файл. Иногда в одном файле находятся определения нескольких
функций, логически связанных друг с другом. Чтобы использовать их в другом исходном
файле, необходим механизм, позволяющий объявить ее, не определяя.
Объявление функции состоит из типа возвращаемого значения, имени и списка параметров.
Вместе эти три элемента составляют прототип. Объявление может появиться в файле
несколько раз.
В нашем примере файл main.C не содержит определений abs(), min() и gcd(), поэтому
вызов любой из них приводит к ошибке компиляции. Чтобы компиляция была успешной,
их необязательно определять, достаточно только объявить:
int abs( int );
int min( int, int );
int gcd( int, int );
(В таком объявлении можно не указывать имя параметра, ограничиваясь названием
типа.)
Объявления (а равно определения встроенных функций ) лучше всего помещать в
заголовочные файлы, которые могут включаться всюду, где необходимо вызвать функцию.
Таким образом, все файлы используют одно общее объявление. Если его необходимо
модифицировать, изменения будут локализованы. Вот так выглядит заголовочный
файл для нашего примера. Назовем его localMath.h:
// определение функции находится в файле gcd.С
int gcd( int, int );
inline int abs(int i) {
return( i<0 ? -i : i );
}
inline int min(int vl.int v2) {
return( vl<v2 ? vl : v2 );
}
В объявлении функции описывается ее интерфейс. Он содержит все данные о том,
какую информацию должна получать функция (список параметров) и какую информацию
она возвращает. Для пользователей важны только эти данные, поскольку лишь они
фигурируют в точке вызова. Интерфейс помещается в заголовочный файл, как мы
поступили с функциями min(), abs() и gcd().
При выполнении наша программа main.C, получив от пользователя значения:
Введите первое значение: 15
Введите второе значение: 123
выдаст следующий результат:
mm: 15
НОД: 3
7.2. Прототип функции
Прототип функции описывает ее интерфейс и состоит из типа возвращаемого функцией
значения, имени и списка параметров. В данном разделе мы детально рассмотрим
эти характеристики.
7.2.1. Тип возвращаемого функцией значения
Тип возвращаемого функцией значения бывает встроенным, как int или double,
составным, как int& или double*, или определенным пользователем – перечислением
или классом. Можно также использовать специальное ключевое слово void, которое
говорит о том, что функция не возвращает никакого значения:
#include <string>
#include <vector> class Date { /* определение */ };
bool look_up( int *, int );
double calc( double );
int count( const string &, char );
Date& calendar( const char );
void sum( vector<int>&, int );
Однако функция или встроенный массив не могут быть типом возвращаемого значения.
Следующий пример ошибочен:
// массив не может быть типом возвращаемого значения
int[10] foo_bar();
Но можно вернуть указатель на первый элемент массива:
// правильно: указатель на первый элемент массива
int *foo_bar();
(Размер массива должен быть известен вызывающей программе.)
Функция может возвращать типы классов, в частности контейнеры. Например:
// правильно: возвращается список символов
list<char> foo_bar();
(Этот подход не очень эффективен. Обсуждение типа возвращаемого значения см.
в разделе 7.4.)
Тип возвращаемого функцией значения должен быть явно указан. Приведенный ниже
код вызывает ошибку компиляции:
// ошибка: пропущен тип возвращаемого значения
const is_equa1( vector<int> vl, vector<int> v2 );
В предыдущих версиях С++ в подобных случаях считалось, что функция возвращает
значение типа int. Стандарт С++ отменил это соглашение. Правильное объявление
is_equal() выглядит так:
// правильно: тип возвращаемого значения указан
const bool is_equa1( vector<int> vl, vector<int> v2 );
7.2.2. Список параметров функции
Список параметров не может быть опущен. Функция, которая не требует параметров,
должна иметь пустой список либо список, состоящий из одного ключевого слова
void. Например, следующие объявления эквивалентны:
int fork();
int fork( void );
Такой список состоит из названий типов, разделенных запятыми. После имени типа
может находиться имя параметра, хотя это и необязательно. В списке параметров
не разрешается использовать сокращенную запись, соотнося одно имя типа с несколькими
параметрами:
int manip( int vl, v2 ); // ошибка
int manip( int vl, int v2 ); // правильно
Имена параметров не могут повторяться. Имена, фигурирующие в определении функции,
можно и даже нужно использовать в ее теле. В объявлении же функции они не обязательны
и служат средством документирования ее интерфейса. Например:
void print( int *array, int size );
Имена параметров в объявлении и в определении одной и той же функции не обязаны
совпадать. Однако употребление разных имен может запутать пользователя.
С++ допускает сосуществование двух или более функций, имеющих одно и то же имя,
но разные списки параметров. Такие функции называются перегруженными. О списке
параметров в этом случае говорят как о сигнатуре функции, поскольку именно он
используется различения разных версий одноименных функций. Имя и сигнатура однозначно
идентифицируют версию. (Перегруженные функции подробно обсуждаются в главе
9.)
7.2.3. Проверка типов формальных параметров
Функция gcd() объявлена следующим образом:
int gcd( int, int );
Объявление говорит о том, что имеется два параметра типа int. Список формальных
параметров предоставляет компилятору информацию, с помощью которой тот может
проверить типы передаваемых функции фактических аргументов.
Что будет, если попытаться вызвать функцию gcd() с аргументами типа char*?
cd( "hello", "world" );
А если передать этой функции не два аргумента, а только один? Или больше двух?
Что случится, если потеряется запятая между числами 24 и 312?
gcd( 24312 );
Единственное разумное поведение компилятора – сообщение об ошибке, поскольку
попытка выполнить такую программу чревата весьма серьезными последствиями. С++
действительно не пропустит подобные вызовы. Текст сообщения будет выглядеть
примерно так:
// gcd( "hello", "world" )
error: invalid argument types ( const char *, const char * ) --
expecting ( int, int )
ошибка: неверные типы аргументов ( const char *, const char * ) --
ожидается ( int, int )
// gcd( 24312 )
error: missing value for second argument
ошибка: пропущено значение второго аргумента
А если вызвать эту функцию с аргументами типа double? Должен ли этот вызов
расцениваться как ошибочный?
gcd( 3.14, 6.29 );
Как было сказано в разделе 4.14, значение типа double
может быть преобразовано в int. Следовательно, считать такой вызов ошибочным
было бы слишком сурово. Вместо этого аргументы неявно преобразуются в int (отбрасыванием
дробной части) и таким образом требования, налагаемые на типы параметров, выполняются.
Поскольку при подобном преобразовании возможна потеря точности, хороший компилятор
выдаст предупреждение.
Вызов превращается в
gcd( 3, 6 );
что дает в результате 3.
С++ является строго типизированным языком. Компилятор проверяет аргументы на
соответствие типов в каждом вызове функции. Если тип фактического аргумента
не соответствует типу формального параметра, то производится попытка неявного
преобразования. Если же это оказывается невозможным или число аргументов неверно,
компилятор выдает сообщение об ошибке. Именно поэтому функция должна быть объявлена
до того, как программа впервые обратится к ней: без объявления компилятор не
обладает информацией для проверки типов.
Пропуск аргумента при вызове или передача аргумента неуказанного типа часто
служили источником ошибок в языке С. Теперь такие погрешности обнаруживаются
на этапе компиляции.
Упражнение 7.1
Какие из следующих прототипов функций содержат ошибки? Объясните.
(a) set( int *, int );
(b) void func();
(c) string error( int );
(d) arr[10] sum( int *, int );
Упражнение 7.2
Напишите прототипы для следующих функций:
Функция с именем compare, имеющая два параметра типа ссылки на класс matrix
и возвращающая значение типа bool.
Функция с именем extract без параметров, возвращающая контейнер set для хранения
значений типа int. (Контейнерный тип set описывался в разделе 6.13.)
Упражнение 7.3
Имеются объявления функций:
double calc( double );
int count( const string &, char );
void sum( vector<int> &, int );
vector<int> vec( 10 );
Какие из следующих вызовов содержат ошибки и почему?
(a) calc( 23.4, 55.1 );
(b) count( "abcda", 'a' );
(c) sum( vec, 43.8 );
(d) calc( 66 );
7.3. Передача аргументов
Функции используют память из стека программы. Некоторая область стека отводится
функции и остается связанной с ней до окончания ее работы, по завершении которой
отведенная ей память освобождается и может быть занята другой функцией. Иногда
эту часть стека называют областью активации.
Каждому параметру функции отводится место в данной области, причем его размер
определяется типом параметра. При вызове функции память инициализируется значениями
фактических аргументов.
Стандартным способом передачи аргументов является копирование их значений, т.е.
передача по значению. При этом способе функция не получает доступа к реальным
объектам, являющихся ее аргументами. Вместо этого она получает в стеке локальные
копии этих объектов. Изменение значений копий никак не отражается на значениях
самих объектов. Локальные копии теряются при выходе из функции.
Значения аргументов при передаче по значению не меняются. Следовательно, программист
не должен заботиться о сохранении и восстановлении их значений при вызове функции.
Без этого механизма любой вызов мог бы привести к нежелательному изменению аргументов,
не объявленных константными явно. Передача по значению освобождает человека
от лишних забот в наиболее типичной ситуации.
Однако такой способ передачи аргументов может не устраивать нас в следующих
случаях:
- передача большого объекта типа класса. Временные и пространственные расходы
на размещение и копирование такого объекта могут оказаться неприемлемыми для
реальной программы; - иногда значения аргументов должны быть модифицированы внутри функции. Например,
swap() должна обменять значения своих аргументов, что невозможно при передаче
по значению:// swap() не меняет значений своих аргументов!
void swap( int vl, int v2 ) {
int tmp = v2;
v2 = vl;
vl = tmp;
}
swap() обменивает значения локальных копий своих аргументов. Те же переменные,
что были использованы в качестве аргументов при вызове, остаются неизменными.
Это можно проиллюстрировать, написав небольшую программу:
#include <iostream>
void swap( int, int );
int main() {
int i = 10;
int j = 20;
cout << "Перед swap():ti: "
<< i << "tj: " << j << endl;
swap( i, j );
cout << "После swap():ti: "
<< i << "tj: " << j << endl;
return 0;
}
Результат выполнения программы:
Перед swap(): i: 10 j: 20
После swap(): i: 10 j: 20
Достичь желаемого можно двумя способами. Первый – объявление параметров указателями.
Вот как будет выглядеть реализация swap() в этом случае:
// pswap() обменивает значения объектов,
// адресуемых указателями vl и v2
void pswap( int *vl, int *v2 ) {
int tmp = *v2;
*v2 = *vl;
*vl = tmp;
}
Функция main() тоже нуждается в модификации. Вместо передачи самих объектов
необходимо передавать их адреса:
pswap( &i, &j );
Теперь программа работает правильно:
Перед swap(): i: 10 j: 20
После swap(): i: 20 j: 10
Альтернативой может стать объявление параметров ссылками. В данном случае реализация
swap() выглядит так:
// rswap() обменивает значения объектов,
// на которые ссылаются vl и v2
void rswap( int &vl, int &v2 ) {
int tmp = v2;
v2 = vl;
vl = tmp;
}
Вызов этой функции из main() аналогичен вызову первоначальной функции swap():
rswap( i, j );
Выполнив программу main(), мы снова получим верный результат.
7.3.1. Параметры-ссылки
Использование ссылок в качестве параметров модифицирует стандартный механизм
передачи по значению. При такой передаче функция манипулирует локальными копиями
аргументов. Используя параметры-ссылки, она получает l-значения своих аргументов
и может изменять их.
В каких случаях применение параметров-ссылок оправданно? Во-первых, тогда, когда
без использования ссылок пришлось бы менять типы параметров на указатели (см.
приведенную выше функцию swap()). Во-вторых, при необходимости вернуть из функции
несколько значений. В-третьих, для передачи большого объекта типа класса. Рассмотрим
два последних случая подробнее.
Как пример функции, использующей параметр-ссылку для возврата дополнительного
значения, возьмем look_up(), которая будет искать заданную величину в векторе
целых чисел. В случае успеха look_up() вернет итератор, указывающий на найденный
элемент, иначе – на элемент, расположенный за конечным. Если величина содержится
в векторе несколько раз, итератор будет указывать на первое вхождение. Кроме
того, дополнительный параметр-ссылка occurs возвращает количество найденных
элементов.
#include
// параметр-ссылка 'occurs'
// содержит второе возвращаемое значение
vector::const_iterator look_up(
const vector &vec,
int value, // искомое значение
int &occurs ) // количество вхождений
{
// res_iter инициализируется значением
// следующего за конечным элемента
vector::const_iterator res_iter = vec.end();
occurs = 0;
for ( vector::const_iterator iter = vec.begin();
iter != vec.end();
++iter )
if ( *iter == value )
{
if ( res_iter == vec.end() )
res_iter = iter;
++occurs;
}
return res_iter;
}
Третий случай, когда использование параметра-ссылки может быть полезно, – это
большой объект типа класса в качестве аргумента. При передаче по значению объект
будет копироваться целиком при каждом вызове функции, что для больших объектов
может привести к потере эффективности. Используя параметр-ссылку, функция получает
доступ к той области памяти, где размещен сам объект, без создания дополнительной
копии. Например:
class Huge { public: double stuff[1000]; };
extern int calc( const Huge & );
int main() {
Huge table[ 1000 ];
// ... инициализация table
int sum = 0;
for ( int ix=0; ix < 1000; ++ix )
// calc() ссылается на элемент массива
// типа Huge
sum += calc( tab1e[ix] );
// ...
}
Может возникнуть желание использовать параметр-ссылку, чтобы избежать создания
копии большого объекта, но в то же время не дать вызываемой функции возможности
изменять значение аргумента. Если параметр-ссылка не должен модифицироваться
внутри функции, то стоит объявить его как ссылку на константу. В такой ситуации
компилятор способен распознать и пресечь попытку непреднамеренного изменения
значения аргумента.
В следующем примере нарушается константность параметра xx функции foo(). Поскольку
параметр функции foo_bar() не является ссылкой на константу, то нет гарантии,
что вызов foo_bar() не изменит значения аргумента. Компилятор сигнализирует
об ошибке:
class X;
extern int foo_bar( X& );
int foo( const X& xx ) {
// ошибка: константа передается
// функции с параметром неконстантного типа
return foo_bar( xx );
}
Для того чтобы программа компилировалась, мы должны изменить тип параметра
foo_bar(). Подойдет любой из следующих двух вариантов:
extern int foo_bar( const X& );
extern int foo_bar( X ); // передача по значению
Вместо этого можно передать копию xx, которую позволено менять:
int foo( const X &xx ) {
// ...
X x2 = xx; // создать копию значения
// foo_bar() может поменять x2,
// xx останется нетронутым
return foo_bar( x2 ); // правильно
}
Параметр-ссылка может именовать любой встроенный тип данных. В частности, разрешается
объявить параметр как ссылку на указатель, если программист хочет изменить значение
самого указателя, а не объекта, который он адресует. Вот пример функции, обменивающей
друг с другом значения двух указателей:
void ptrswap( int *&vl, int *&v2 ) {
int *trnp = v2;
v2 = vl;
vl = tmp;
}
Объявление
int *&v1;
должно читаться справа налево: v1 является ссылкой на указатель на объект типа
int. Модифицируем функцию main(), которая вызывала rswap(), для проверки работы
ptrswap():
#include <iostream>
void ptrswap( int *&vl, int *&v2 );
int main() {
int i = 10;
int j = 20;
int *pi = &i;
int *pj = &j;
cout << "Перед ptrswap():tpi: "
<< *pi << "tpj: " << *pj << endl;
ptrswap( pi, pj );
cout << "После ptrswap():tpi: "
<< *pi << "tpj: " << pj << endl;
return 0;
}
Вот результат работы программы:
Перед ptrswap(): pi: 10 pj: 20
После ptrswap(): pi: 20 pj: 10
7.3.2. Параметры-ссылки и параметры-указатели
Когда же лучше использовать параметры-ссылки, а когда – параметры-указатели?
В конце концов, и те и другие позволяют функции модифицировать объекты, эффективно
передавать в функцию большие объекты типа класса. Что выбрать: объявить параметр
ссылкой или указателем?
Как было сказано в разделе 3.6, ссылка может быть один
раз инициализирована значением объекта, и впоследствии изменить ее нельзя. Указатель
же в течение своей жизни способен адресовать разные объекты или не адресовать
вообще.
Поскольку указатель может содержать, а может и не содержать адрес какого-либо
объекта, перед его использованием функция должна проверить, не равен ли он нулю:
class X;
void manip( X *px )
{
// проверим на 0 перед использованием
if ( px != 0 )
// обратимся к объекту по адресу...
}
Параметр-ссылка не нуждается в этой проверке, так как всегда существует именуемый
ею объект. Например:
class Type { };
void operate( const Type& p1, const Type& p2 );
int main() {
Type obj1;
// присвоим objl некоторое значение
// ошибка: ссылка не может быть равной 0
Type obj2 = operate( objl, 0 );
}
Если параметр должен ссылаться на разные объекты во время выполнения функции
или принимать нулевое значение (ни на что не ссылаться), нам следует использовать
указатель.
Одна из важнейших сфер применения параметров-ссылок – эффективная реализация
перегруженных операций. При этом использование операций остается простым и интуитивно
понятным. (Подробнее данный вопрос рассматривается в главе 15.) Разберем маленький
пример. Представим себе класс Matrix (матрица). Хорошо бы реализовать операции
сложения и присваивания “привычным” способом:
Matrix a, b, c;
c = a + b;
Эти операции реализуются с помощью перегруженных операторов – функций с немного
необычным именем. Для оператора сложения такая функция будет называться operator+.
Посмотрим, как ее определить:
Matrix // тип возврата - Matrix
operator+( // имя перегруженного оператора
Matrix m1, // тип левого операнда
Matrix m2 // тип правого операнда
)
{
Matrix result;
// необходимые действия
return result;
}
При такой реализации сложение двух объектов типа Matrix выглядит вполне привычно:
a + b;
но, к сожалению, оказывается совершенно неэффективным. Заметим, что параметры
у нас передаются по значению. Содержимое двух матриц будет копироваться в область
активации функции operator+(), а поскольку объекты типа Matrix весьма велики,
затраты времени и памяти на создание копий могут быть совершенно неприемлемыми.
Представим себе, что мы решили использовать указатели в качестве параметров,
чтобы избежать этих затрат. Вот модифицированный код operator+():
// реализация с параметрами-указателями
operator+( Matrix *ml, Matrix *m2 )
{
Matrix result;
// необходимые действия
return result;
}
Да, мы добились эффективной реализации, но зато теперь применение нашей операции
вряд ли можно назвать интуитивно понятным. В качестве значений параметров-указателей
требуется передавать адреса складываемых объектов. Поэтому для сложения двух
матриц пришлось бы написать:
&a + &b; // допустимо, хотя и плохо
Хотя такая форма не может не вызвать критику, но все-таки два объекта сложить
еще удается. А вот три уже крайне затруднительно:
// а вот это не работает
// &a + &b возвращает объект типа Matrix
&a + &b + &c;
Для того чтобы сложить три объекта, при подобной реализации нужно написать
так:
// правильно: работает, однако ...
&( &a + &b ) + &c;
Трудно ожидать, что кто-нибудь согласится писать такие выражения. К счастью,
параметры-ссылки дают именно то решение, которое требуется. Если параметр объявлен
как ссылка, функция получает его l-значение, а не копию. Лишнее копирование
исключается. И тип фактического аргумента может быть Matrix – это упрощает операцию
сложения, как и для встроенных типов. Вот схема перегруженного оператора сложения
для класса Matrix:
// реализация с параметрами-ссылками
operator+( const Matrix &m1, const Matrix &m2 )
{
Matrix result;
// необходимые действия
return result;
}
При такой реализации сложение трех объектов Matrix выглядит вполне привычно:
a + b + c;
Ссылки были введены в С++ именно для того, чтобы удовлетворить двум требованиям:
эффективная реализация и интуитивно понятное применение.
7.3.3. Параметры-массивы
Массив в С++ никогда не передается по значению, а только как указатель на его
первый, точнее нулевой, элемент. Например, объявление
void putValues( int[ 10 ] );
рассматривается компилятором так, как будто оно имеет вид
void putValues( int* );
Размер массива неважен при объявлении параметра. Все три приведенные записи
эквивалентны:
// три эквивалентных объявления putValues()
void putValues( int* );
void putValues( int[] );
void putValues( int[ 10 ] );
Передача массивов как указателей имеет следующие особенности:
- • изменение значения аргумента внутри функции затрагивает сам переданный
объект, а не его локальную копию. Если такое поведение нежелательно, программист
должен позаботиться о сохранении исходного значения. Можно также при объявлении
функции указать, что она не должна изменять значение параметра, объявив этот
параметр константой:void putValues( const int[ 10 ] ); - размер массива не является частью типа параметра. Поэтому функция не знает
реального размера передаваемого массива. Компилятор тоже не может это проверить.
Рассмотрим пример:void putValues( int[ 10 ] ); // рассматривается как int*
int main() {
int i, j [ 2 ];
putValues( &i ); // правильно: &i is int*;
// однако при выполнении возможна ошибка
putValues( j ); // правильно: j - адрес 0-го элемента - int*;
// однако при выполнении возможна ошибка
При проверке типов параметров компилятор способен распознать, что в обоих случаях
тип аргумента int* соответствует объявлению функции. Однако контроль за тем,
не является ли аргумент массивом, не производится.
По принятому соглашению C-строка является массивом символов, последний элемент
которого равен нулю. Во всех остальных случаях при передаче массива в качестве
параметра необходимо указывать его размер. Это относится и к массивам символов,
внутри которых встречается 0. Обычно для такого указания используют дополнительный
параметр функции. Например:
void putValues( int[], int size );
int main() {
int i, j[ 2 ];
putValues( &i, 1 );
putValues( j, 2 );
return 0;
}
putValues() печатает элементы массива в следующем формате:
( 10 )< 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 >
где 10 – это размер массива. Вот как выглядит реализация putValues(), в которой
используется дополнительный параметр:
#include <iostream>
const lineLength =12; // количество элементов в строке
void putValues( int *ia, int sz )
{
cout << "( " << sz << " )< ";
for (int i=0;i<sz; ++i )
{
if ( i % lineLength == 0 && i )
cout << "nt"; // строка заполнена
cout << ia[ i ];
// разделитель, печатаемый после каждого элемента,
// кроме последнего
if ( i % lineLength != lineLength-1 &&
i != sz-1 )
cout << ", ";
}
cout << " >n";
}
Другой способ сообщить функции размер массива-параметра – объявить параметр
как ссылку. В этом случае размер становится частью типа, и компилятор может
проверить аргумент в полной мере.
// параметр - ссылка на массив из 10 целых
void putValues( int (&arr)[10] );
int main() {
int i, j [ 2 ];
putValues(i); // ошибка:
// аргумент не является массивом из 10 целых
putValues(j); // ошибка:
// аргумент не является массивом из 10 целых
return 0;
}
Поскольку размер массива теперь является частью типа параметра, новая версия
putValues() способна работать только с массивами из 10 элементов. Конечно, это
ограничивает ее область применения, зато реализация значительно проще:
#include <iostream>
void putValues( int (&ia)[10] )
{
cout << "( 10 )< ";
for ( int 1 =0; i < 10; ++i ) { cout << ia[ i ];
// разделитель, печатаемый после каждого элемента,
// кроме последнего
if ( i != 9 )
cout << ", ";
}
cout << " >n";
}
Еще один способ получить размер переданного массива в функции – использовать
абстрактный контейнерный тип. (Такие типы были представлены в главе 6. В следующем
подразделе мы поговорим об этом подробнее.)
Хотя две предыдущих реализации putValues() правильны, они обладают серьезными
недостатками. Так, первый вариант работает только с массивами типа int. Для
типа double* нужно писать другую функцию, для long* – еще одну и т.д. Второй
вариант производит операции только над массивом из 10 элементов типа int. Для
обработки массивов разного размера нужны дополнительные функции. Лучшим решением
было бы использовать шаблон – функцию, или, скорее, обобщенную реализацию кода
целого семейства функций, которые отличаются только типами обрабатываемых данных.
Вот как можно сделать из первого варианта putValues() шаблон, способный работать
с массивами разных типов и размеров:
template <class Type>
void putValues( Type *ia, int sz )
{
// так же, как и раньше
}
Параметры шаблона заключаются в угловые скобки. Ключевое слово class означает,
что идентификатор Type служит именем параметра, при конкретизации шаблона функции
putValues() он заменяется на реальный тип – int, double, string и т.д. (В главе
10 мы продолжим разговор о шаблонах функций.)
Параметр может быть многомерным массивом. Для такого параметра должны быть заданы
правые границы всех измерений, кроме первого. Например:
putValues( int matrix[][10], int rowSize );
Здесь matrix объявляется как двумерный массив, который содержит десять столбцов
и неизвестное число строк. Эквивалентным объявлением для matrix будет:
int (*matrix)[10]
Многомерный массив передается как указатель на его нулевой элемент. В нашем
случае тип matrix – указатель на массив из десяти элементов типа int. Как и
для одномерного массива, граница первого измерения не учитывается при проверке
типов. Если параметры являются многомерными массивами, то контролируются все
измерения, кроме первого.
Заметим, что скобки вокруг *matrix необходимы из-за более высокого приоритета
операции взятия индекса. Инструкция
int *matrix[10];
объявляет matrix как массив из десяти указателей на int.
7.3.4. Абстрактные контейнерные типы в качестве параметров
Абстрактные контейнерные типы, представленные в главе 6, также используются
для объявления параметров функции. Например, можно определить putValues() как
имеющую параметр типа vector<int> вместо встроенного типа массива.
Контейнерный тип является классом и обеспечивает значительно большую функциональность,
чем встроенные массивы. Так, vector<int> “знает” собственный размер. В
предыдущем подразделе мы видели, что размер параметра-массива неизвестен функции
и для его передачи приходится задавать дополнительный параметр. Использование
vector<int> позволяет обойти это ограничение. Например, можно изменить
определение нашей putValues() на такое:
#include <iostream>
#include <vector>
const lineLength =12; // количество элементов в строке
void putValues( vector<int> vec )
{
cout << "( " << vec.size() << " )< ";
for ( int i = 0; i < vec.size(); ++1 ) {
if ( i % lineLength == 0 && i )
cout << "nt"; // строка заполнена
cout << vec[ i ];
// разделитель, печатаемый после каждого элемента,
// кроме последнего
if ( 1 % lineLength != lineLength-1 &&
i != vec.size()-1 )
cout << ", ";
}
cout << " >n";
}
Функция main(), вызывающая нашу новую функцию putValues(), выглядит так:
void putValues( vector<int> );
int main() {
int i, j[ 2 ];
// присвоить i и j некоторые значения
vector<int> vec1(1); // создадим вектор из 1 элемента
vecl[0] = i;
putValues( vecl );
vector<int> vec2; // создадим пустой вектор
// добавим элементы к vec2
for ( int ix = 0;
ix < sizeof( j ) / sizeof( j[0] );
++ix )
// vec2[ix] == j [ix]
vec2.push_back( j[ix] );
putValues( vec2 );
return 0;
}
Заметим, что параметр putValues()передается по значению. В подобных случаях
контейнер со всеми своими элементами всегда копируется в стек вызванной функции.
Поскольку операция копирования весьма неэффективна, такие параметры лучше объявлять
как ссылки.
Как бы вы изменили объявление putValues()?
Вспомним, что если функция не модифицирует значение своего параметра, то предпочтительнее,
чтобы он был ссылкой на константный тип:
void putValues( const vector<int> & ) { ...
7.3.5. Значения параметров по умолчанию
Значение параметра по умолчанию – это значение, которое разработчик считает
подходящим в большинстве случаев употребления функции, хотя и не во всех. Оно
освобождает программиста от необходимости уделять внимание каждой детали интерфейса
функции.
Значения по умолчанию для одного или нескольких параметров функции задаются
с помощью того же синтаксиса, который употребляется при инициализации переменных.
Например, функция для создания и инициализации двумерного массива, моделирующего
экран терминала, может использовать такие значения для высоты, ширины и символа
фона экрана:
char *screenInit( int height = 24, int width = 80,
char background = ' ' );
Функция, для которой задано значение параметра по умолчанию, может вызываться
по-разному. Если аргумент опущен, используется значение по умолчанию, в противном
случае – значение переданного аргумента. Все следующие вызовы screenInit() корректны:
char *cursor;
// эквивалентно screenInit(24,80,' ')
cursor = screenInit();
// эквивалентно screenInit(66,80,' ')
cursor = screenlnit(66);
// эквивалентно screenInit(66,256,' ')
cursor = screenlnit(66, 256);
cursor = screenlnit(66, 256, '#');
Фактические аргументы сопоставляются с формальными параметрами позиционно (в
порядке следования), и значения по умолчанию могут использоваться только для
подстановки вместо отсутствующих последних аргументов. В нашем примере невозможно
задать значение для
background, не задавая его для height и width.
// эквивалентно screenInit('?',80,' ')
cursor = screenInit('?');
// ошибка, неэквивалентно screenInit(24,80,'?')
cursor = screenInit( , ,'?');
При разработке функции с параметрами по умолчанию придется позаботиться об
их расположении. Те, для которых значения по умолчанию вряд ли будут употребляться,
необходимо поместить в начало списка. Функция screenInit() предполагает (возможно,
основываясь на опыте применения), что параметр height будет востребован пользователем
наиболее часто.
Значения по умолчанию могут задаваться для всех параметров или только для некоторых.
При этом параметры без таких значений должны идти раньше тех, для которых они
указаны.
// ошибка: width должна иметь значение по умолчанию,
// если такое значение имеет height
char *screenlnit( int height = 24, int width,
char background = ' ' );
Значение по умолчанию может указываться только один раз в файле. Следующая
запись ошибочна:
// tf.h
int ff( int = 0 );
// ft.С
#include "ff.h"
int ff( int i = 0) { ... } // ошибка
По соглашению значение задается в объявлении функции, которое размещается в
общедоступном заголовочном файле (описывающем интерфейс), а не в ее определении.
Если же указать его в определении, это значение будет доступно только для вызовов
функции внутри исходного файла, содержащего это определение.
Можно объявить функцию повторно и таким образом задать дополнительные параметры
по умолчанию. Это удобно при настройке универсальной функции для конкретного
приложения. Скажем, в системной библиотеке UNIX есть функция chmod(), изменяющая
режим доступа к файлу. Ее объявление содержится в системном заголовочном файле
<cstdlib>:
int chmod( char *filePath, int protMode );
protMode представляет собой режим доступа, а filePath – имя и каталог файла.
Если в некотором приложении файл только читается, можно переобъявить функцию
chmod(), задав для соответствующего параметра значение по умолчанию, чтобы не
указывать его при каждом вызове:
#include <cstdlib>
int chmod( char *filePath, int protMode=0444 );
Если функция объявлена в заголовочном файле так:
file int ff( int a, int b, int с = 0 ); // ff.h
то как переобъявить ее, чтобы присвоить значение по умолчанию для параметра
b? Следующая строка ошибочна, поскольку она повторно задает значение для с:
#include "ff.h"
int ff( int a, int b = 0, int с = 0 ); // ошибка
Так выглядит правильное объявление:
#include "ff.h"
int ff( int a, int b = 0, int с ); // правильно
В том месте, где мы переобъявляем функцию ff(), параметр b расположен правее
других, не имеющих значения по умолчанию. Поэтому требование присваивать такие
значения справа налево не нарушается. Теперь мы можем переобъявить ff() еще
раз:
#include "ff.h"
int ff( int a, int b = 0, int с ); // правильно
int ff( int a = 0, int b, int с ); // правильно
Значение по умолчанию не обязано быть константным выражением, можно использовать
любое:
int aDefault();
int bDefault( int );
int cDefault( double = 7.8 );
int glob;
int ff( int a = aDefault() ,
int b = bDefau1t( glob ) ,
int с = cDefault() );
Если такое значение является выражением, то оно вычисляется во время вызова
функции. В примере выше cDefault() работает каждый раз, когда происходит вызов
функции ff() без указания третьего аргумента.
7.3.6. Многоточие
Иногда нельзя перечислить типы и количество всех возможных аргументов функции.
В этих случаях список параметров представляется многоточием (…), которое отключает
механизм проверки типов. Наличие многоточия говорит компилятору, что у функции
может быть произвольное количество аргументов неизвестных заранее типов. Многоточие
употребляется в двух форматах:
void foo( parm_list, ... );
void foo( ... );
Первый формат предоставляет объявления для части параметров. В этом случае
проверка типов для объявленных параметров производится, а для оставшихся фактических
аргументов – нет. Запятая после объявления известных параметров необязательна.
Примером вынужденного использования многоточия служит функция printf() стандартной
библиотеки С. Ее первый параметр является C-строкой:
int printf( const char* ... );
Это гарантирует, что при любом вызове printf() ей будет передан первый аргумент
типа const char*. Содержание такой строки, называемой форматной, определяет,
необходимы ли дополнительные аргументы при вызове. При наличии в строке формата
метасимволов, начинающихся с символа %, функция ждет присутствия этих аргументов.
Например, вызов
printf( "hello, worldn" );
имеет один строковый аргумент. Но
printf( "hello, %sn", userName );
имеет два аргумента. Символ % говорит о наличии второго аргумента, а буква
s, следующая за ним, определяет его тип – в данном случае символьную строку.
Большинство функций с многоточием в объявлении получают информацию о типах и
количестве фактических параметров по значению явно объявленного параметра. Следовательно,
первый формат многоточия употребляется чаще.
Отметим, что следующие объявления неэквивалентны:
void f();
void f( ... );
В первом случае f() объявлена как функция без параметров, во втором – как имеющая
ноль или более параметров. Вызовы
f( someValue );
f( cnt, a, b, с );
корректны только для второго объявления. Вызов
f();
применим к любой из двух функций.
Упражнение 7.4
Какие из следующих объявлений содержат ошибки? Объясните.
(a) void print( int arr[][], int size );
(b) int ff( int a, int b = 0, int с = 0 );
(c) void operate( int *matrix[] );
(d) char *screenInit( int height = 24, int width,
char background );
(e) void putValues( int (&ia)[] );
Упражнение 7.5
Повторные объявления всех приведенных ниже функций содержат ошибки. Найдите
их.
(a) char *screenInit( int height, int width,
char background = ‘ ‘ );
char *screenInit( int height = 24, int width,
char background );
(b) void print( int (*arr)[6], int size );
void print( int (*arr)[5], int size );
(c) void manip( int *pi, int first, int end = 0 );
void manip( int *pi, int first = 0, int end = 0 );
Упражнение 7.6
Даны объявления функций.
void print( int arr[][5], int size );
void operate(int *matrix[7]);
char *screenInit( int height = 24, int width = 80,
char background = ‘ ‘ );
Вызовы этих функций содержат ошибки. Найдите их и объясните.
(a) screenInit();
(b) int *matrix[5];
operate( matrix );
(c) int arr[5][5];
print( arr, 5 );
Упражнение 7.7
Перепишите функцию putValues( vector<int> ), приведенную в подразделе
7.3.4, так, чтобы она работала с контейнером list<string>. Печатайте по
одному значению на строке. Вот пример вывода для списка из двух строк:
( 2 )
<
«first string»
«second string»
>
Напишите функцию main(), вызывающую новый вариант putValues() со следующим
списком строк:
"put function declarations in header files"
"use abstract container types instead of built-in arrays"
"declare class parameters as references"
"use reference to const types for invariant parameters"
"use less than eight parameters"
Упражнение 7.8
В каком случае вы применили бы параметр-указатель? А в каком – параметр-ссылку?
Опишите достоинства и недостатки каждого способа.
7.4. Возврат значения
В теле функции может встретиться инструкция return. Она завершает выполнение
функции. После этого управление возвращается той функции, из которой была вызвана
данная. Инструкция return может употребляться в двух формах:
return;
return expression;
Первая форма используется в функциях, для которых типом возвращаемого значения
является void. Использовать return в таких случаях обязательно, если нужно принудительно
завершить работу. (Такое применение return напоминает инструкцию break, представленную
в разделе 5.8.) После конечной инструкции функции подразумевается
наличие return. Например:
void d_copy( double "src, double *dst, int sz )
{
/* копируем массив "src" в "dst"
* для простоты предполагаем, что они одного размера
*/
// завершение, если хотя бы один из указателей равен 0
if ( !src || !dst )
return;
// завершение,
// если указатели адресуют один и тот же массив
if ( src == dst )
return;
// копировать нечего
if ( sz == 0 )
return;
// все еще не закончили?
// тогда самое время что-то сделать
for ( int ix = 0; ix < sz; ++ix )
dst[ix] = src[ix];
// явного завершения не требуется
}
Во второй форме инструкции return указывается то значение, которое функция
должна вернуть. Это значение может быть сколь угодно сложным выражением, даже
содержать вызов функции. В реализации функции factorial(), которую мы рассмотрим
в следующем разделе, используется return следующего вида:
return val * factorial(val-1);
В функции, не объявленная с void в качестве типа возвращаемого значения, обязательно
использовать вторую форму return, иначе произойдет ошибка компиляции. Хотя компилятор
не отвечает за правильность результата, он сможет гарантировать его наличие.
Следующая программа не компилируется из-за двух мест, где программа завершается
без возврата значения:
// определение интерфейса класса Matrix
#include "Matrix.h"
bool is_equa1( const Matrix &ml, const Matrix &m2 )
{
/* Если содержимое двух объектов Matrix одинаково,
* возвращаем true;
* в противном случае - false
*/
// сравним количество столбцов
if ( ml.colSize() != m2.co1Size() )
// ошибка: нет возвращаемого значения
return;
// сравним количество строк
if ( ml.rowSize() != m2.rowSize() )
// ошибка: нет возвращаемого значения
return;
// пробежимся по обеим матрицам, пока
// не найдем неравные элементы
for ( int row = 0; row < ml.rowSize(); ++row )
for ( int col = 0; co1 < ml.colSize(); ++co1 )
if ( ml[row][col] != m2[row][col] )
return false;
// ошибка: нет возвращаемого значения
// для случая равенства
}
Если тип возвращаемого значения не точно соответствует указанному в объявлении
функции, то применяется неявное преобразование типов. Если же стандартное приведение
невозможно, происходит ошибка компиляции. (Преобразования типов рассматривались
в разделе 4.1.4.)
По умолчанию возвращаемое значение передается по значению, т.е. вызывающая функция
получает копию результата вычисления выражения, указанного в инструкции return.
Например:
Matrix grow( Matrix* p ) {
Matrix val;
// ...
return val;
}
grow() возвращает вызывающей функции копию значения, хранящегося в переменной
val.
Такое поведение можно изменить, если объявить, что возвращается указатель или
ссылка. При возврате ссылки вызывающая функция получает l-значение для val и
потому может модифицировать val или взять ее адрес. Вот как можно объявить,
что grow() возвращает ссылку:
Matrix& grow( Matrix* p ) {
Matrix *res;
// выделим память для объекта Matrix
// большого размера
// res адресует этот новый объект
// скопируем содержимое *p в *res
return *res;
}
Если возвращается большой объект, то гораздо эффективнее перейти от возврата
по значению к использованию ссылки или указателя. В некоторых случаях компилятор
может сделать это автоматически. Такая оптимизация получила название именованное
возвращаемое значение. (Она описывается в разделе 14.8.)
Объявляя функцию как возвращающую ссылку, программист должен помнить о двух
возможных ошибках:
• возврат ссылки на локальный объект, время жизни которого ограничено временем
выполнения функции. (О времени жизни локальных объектов речь пойдет в разделе
8.3.) По завершении функции такой ссылке соответствует область памяти, содержащая
неопределенное значение. Например:
// ошибка: возврат ссылки на локальный объект
Matrix& add( Matrix &m1, Matrix &m2 )
{
Matrix result:
if ( m1.isZero() )
return m2;
if ( m2.isZero() )
return m1;
// сложим содержимое двух матриц
// ошибка: ссылка на сомнительную область памяти
// после возврата
return result;
}
В таком случае тип возврата не должен быть ссылкой. Тогда локальная переменная
может быть скопирована до окончания времени своей жизни:
Matrix add( … )
• функция возвращает l-значение. Любая его модификация затрагивает сам объект.
Например:
#include <vector>
int &get_val( vector<int> &vi, int ix ) {
return vi [ix];
}
int ai[4] = { 0, 1, 2, 3 };
vector<int> vec( ai, ai+4 ); // копируем 4 элемента ai в vec
int main() {
// увеличивает vec[0] на 1
get_val( vec.0 )++;
// …
}
Для предотвращения нечаянной модификации возвращенного объекта нужно объявить
тип возврата как const:
const int &get_val( … )
Примером ситуации, когда l-значение возвращается намеренно, чтобы позволить
модифицировать реальный объект, может служить перегруженный оператор взятия
индекса для класса IntArray из раздела 2.3.
7.4.1. Передача данных через параметры и через глобальные объекты
Различные функции программы могут общаться между собой с помощью двух механизмов.
(Под словом “общаться” мы подразумеваем обмен данными.) В одном случае используются
глобальные объекты, в другом – передача параметров и возврат значений.
Глобальный объект определен вне функции. Например:
int glob;
int main() {
// что угодно
}
Объект glob является глобальным. (В главе 8 рассмотрение глобальных объектов
и глобальной области видимости будет продолжено.) Главное достоинство и одновременно
один из наиболее заметных недостатков такого объекта – доступность из любого
места программы, поэтому его обычно используют для общения между разными модулями.
Обратная сторона медали такова:
- функции, использующие глобальные объекты, зависят от этих объектов и их
типов. Использовать такую функцию в другом контексте затруднительно; - при модификации такой программы повышается вероятность ошибок. Даже для
внесения локальных изменений необходимо понимание всей программы в целом; - если глобальный объект получает неверное значение, ошибку нужно искать
по всей программе. Отсутствует локализация; - используя глобальные объекты, труднее писать рекурсивные функции (Рекурсия
возникает тогда, когда функция вызывает сама себя. Мы рассмотрим это в разделе
7.5.); - если используются потоки (threads), то для синхронизации доступа к глобальным
объектам требуется писать дополнительный код. Отсутствие синхронизации – одна
из распространенных ошибок при использовании потоков. (Пример использования
потоков при программировании на С++ см. в статье “Distributing Object Computing
in C++” (Steve Vinoski and Doug Schmidt) в [LIPPMAN96b].)
Можно сделать вывод, что для передачи информации между функциями предпочтительнее
пользоваться параметрами и возвращаемыми значениями.
Вероятность ошибок при таком подходе возрастает с увеличением списка. Считается,
что восемь параметров – это приемлемый максимум. В качестве альтернативы длинному
списку можно использовать в качестве параметра класс, массив или контейнер.
Он способен содержать группу значений.
Аналогично программа может возвращать только одно значение. Если же логика требует
нескольких, некоторые параметры объявляются ссылками, чтобы функция могла непосредственно
модифицировать значения соответствующих фактических аргументов и использовать
эти параметры для возврата дополнительных значений, либо некоторый класс или
контейнер, содержащий группу значений, объявляется типом, возвращаемым функцией.
Упражнение 7.9
Каковы две формы инструкции return? Объясните, в каких случаях следует использовать
первую, а в каких вторую форму.
Упражнение 7.10
Найдите в данной функции потенциальную ошибку времени выполнения:
vector<string> &readText( ) {
vector<string> text;
string word;
while ( cin >> word ) {
text.push_back( word );
// ...
}
// ....
return text;
}
Упражнение 7.11
Каким способом вы вернули бы из функции несколько значений? Опишите достоинства
и недостатки вашего подхода
7.5. Рекурсия
Функция, которая прямо или косвенно вызывает сама себя, называется рекурсивной.
Например:
int rgcd( int vl, int v2 )
{
if ( v2 != 0 )
return rgcd( v2, vl%v2 );
return vl;
}
Такая функция обязательно должна определять условие окончания, в противном
случае рекурсия будет продолжаться бесконечно. Подобную ошибку так иногда и
называют – бесконечная рекурсия. Для rgcd() условием окончания является равенство
нулю остатка.
Вызов
rgcd( 15, 123 );
возвращает 3 (см. табл. 7.1).
Таблица 7.1. Трассировка вызова rgcd (15,123)
| v1 | v2 | return |
| 15 | 123 | rgcd(123,15) |
| 123 | 15 | rgcd(15,3) |
| 15 | 3 | rgcd(3,0) |
| 3 | 0 | 3 |
Последний вызов,
rgcd(3,0);
удовлетворяет условию окончания. Функция возвращает наибольший общий делитель,
он же возвращается и каждым предшествующим вызовом. Говорят, что значение всплывает
(percolates) вверх, пока управление не вернется в функцию, вызвавшую rgcd()
в первый раз.
Рекурсивные функции обычно выполняются медленнее, чем их нерекурсивные (итеративные)
аналоги. Это связано с затратами времени на вызов функции. Однако, как правило,
они компактнее и понятнее.
Приведем пример. Факториалом числа n является произведение натуральных чисел
от 1 до n. Так, факториал 5 равен 120: 1 ? 2 ? 3 ? 4 ? 5 = 120.
Вычислять факториал удобно с помощью рекурсивной функции:
unsigned long
factorial( int val ) {
if ( val > 1 )
return val * factorial( val-1 );
return 1;
}
Рекурсия обрывается по достижении val значения 1.
Упражнение 7.12
Перепишите factorial() как итеративную функцию.
Упражнение 7.13
Что произойдет, если условием окончания factorial() будет следующее:
if ( val != 0 )
7.6. Встроенные функции
Рассмотрим следующую функцию min():
int min( int vl, int v2 )
{
return( vl < v2 ? vl : v2 );
}
Преимущества определения функции для такой небольшой операции таковы:
- как правило, проще прочесть и интерпретировать вызов min(), чем читать
условный оператор и вникать в смысл его действий, особенно если v1 и v2 являются
сложными выражениями; - модифицировать одну локализованную реализацию в приложении легче, чем 300.
Например, если будет решено изменить проверку на:( vl == v2 || vl < v2 )
поиск каждого ее вхождения будет утомительным и с большой долей вероятности
приведет к ошибкам; - семантика единообразна. Все проверки выполняются одинаково;
- функция может быть повторно использована в другом приложении.
Однако этот подход имеет один недостаток: вызов функции происходит медленнее,
чем непосредственное вычисление условного оператора. Необходимо скопировать
два аргумента, запомнить содержимое машинных регистров и передать управление
в другое место программы. Решение дают встроенные функции. Встроенная функция
“подставляется по месту” в каждой точке своего вызова. Например:
int minVa12 = min( i, j );
заменяется при компиляции на
int minVal2 = i < j ? i : j;
Таким образом, не требуется тратить время на реализацию min() в виде функции.
Функция min() объявляется как встроенная с помощью ключевого слова inline перед
типом возвращаемого значения в объявлении или определении:
inline int min( int vl, int v2 ) { /* ... */ }
Заметим, однако, что спецификация inline – это только подсказка компилятору.
Компилятор может проигнорировать ее, если функция плохо подходит для встраивания
по месту. Например, рекурсивная функция (такая, как rgcd()) не может быть полностью
встроена в месте вызова (хотя для самого первого вызова это возможно). Функция
из 1200 строк также скорее всего не подойдет. В общем случае такой механизм
предназначен для оптимизации небольших, простых, часто используемых функций.
Он крайне важен для поддержки концепции сокрытия информации при разработке абстрактных
типов данных. Например, встроенной объявлена функция-член size() в классе IntArray
из раздела 2.3.
Встроенная функция должна быть видна компилятору в месте вызова. В отличие от
обычной, такая функция определяется в каждом исходном файле, где есть обращения
к ней. Конечно же, определения одной и той же встроенной функции в разных файлах
должны совпадать. Если программа содержит два исходных файла compute.C и draw.C,
не нужно писать для них разные реализации функции min(). Если определения функции
различаются, программа становится нестабильной: неизвестно, какое из них будет
выбрано для каждого вызова, если компилятор не стал встраивать эту функцию.
Рекомендуется помещать определение встроенной функции в заголовочный файл и
включать его во все файлы, где есть обращения к ней. Такой подход гарантирует,
что для встроенной функции существует только одно определение и код не дублируется;
дублирование может привести к непреднамеренному расхождению текстов в течение
жизненного цикла программы.
Поскольку min() является общеупотребительной операцией, реализация ее входит
в стандартную библиотеку С++; это один из обобщенных алгоритмов, описанных в
главе 12 и в Приложении. Функция min() реализована как шаблон, что позволяет
ей работать с операндами арифметического типа, отличного от int. (Шаблоны функций
рассматриваются в главе 10.)
7.7. Директива связывания extern «C» A
Если программист хочет использовать функцию, написанную на другом языке, в
частности на С, то компилятору нужно указать, что при вызове требуются несколько
иные условия. Скажем, имя функции или порядок передачи аргументов различаются
в зависимости от языка программирования.
Показать, что функция написана на другом языке, можно с помощью директивы связывания
в форме простой либо составной инструкции:
// директива связывания в форме простой инструкции
extern "C" void exit(int);
// директива связывания в форме составной инструкции
extern "C" {
int printf( const char* ... );
int scanf( const char* ... );
}
// директива связывания в форме составной инструкции
extern "C" {
#include <cmath>
}
Первая форма такой директивы состоит из ключевого слова extern, за которым
следует строковый литерал, а за ним – “обычное” объявление функции. Хотя функция
написана на другом языке, проверка типов вызова выполняется полностью. Несколько
объявлений функций могут быть помещены в фигурные скобки составной инструкции
директивы связывания – второй формы этой директивы. Скобки отмечают те объявления,
к которым она относится, не ограничивая их видимости, как в случае обычной составной
инструкции. Составная инструкция extern «C» в предыдущем примере говорит
только о том, что функции printf() и scanf() написаны на языке С. Во всех остальных
отношениях эти объявления работают точно так же, как если бы они были расположены
вне инструкции.
Если в фигурные скобки составной директивы связывания помещается директива препроцессора
#include, все объявленные во включаемом заголовочном файле функции рассматриваются
как написанные на языке, указанном в этой директиве. В предыдущем примере все
функции из заголовочного файла cmath написаны на языке С.
Директива связывания не может появиться внутри тела функции. Следующий фрагмент
кода вызывает ошибку компиляции:
int main() {
// ошибка: директива связывания не может появиться
// внутри тела функции
extern "C" double sqrt( double );
double getValue(); //правильно
double result = sqrt ( getValue() );
//...
return 0;
}
Если мы переместим директиву так, чтобы она оказалась вне тела main(), программа
откомпилируется правильно:
extern "C" double sqrt( double );
int main() {
double getValue(); //правильно
double result = sqrt ( getValue() );
//...
return 0;
}
Однако более подходящее место для директивы связывания – заголовочный файл,
где находится объявление функции, описывающее ее интерфейс.
Как сделать С++ функцию доступной для программы на С? Директива extern «C»
поможет и в этом:
// функция calc() может быть вызвана из программы на C
extern «C» double calc( double dparm ) { /* … */ }
Если в одном файле имеется несколько объявлений функции, то директива связывания
может быть указана при каждом из них или только при первом – в этом случае она
распространяется и на все последующие объявления. Например:
// —- myMath.h —-
extern «C» double calc( double );
// —- myMath.C —-
// объявление calc() в myMath.h
#include «myMath.h»
// определение функции extern «C» calc()
// функция calc() может быть вызвана из программы на C
double calc( double dparm ) { // … }
В данном разделе мы видели примеры директивы связывания extern «C»
только для языка С. Это единственный внешний язык, поддержку которого гарантирует
стандарт С++. Конкретная реализация может поддерживать связь и с другими языками.
Например, extern «Ada» для функций, написанных на языке Ada; extern
«FORTRAN» для языка FORTRAN и т.д. Мы описали один из случаев использования
ключевого слова extern в С++. В разделе 8.2 мы покажем,
что это слово имеет и другое назначение в объявлениях функций и объектов.
Упражнение 7.14
exit(), printf(), malloc(), strcpy() и strlen() являются функциями из библиотеки
С. Модифицируйте приведенную ниже С-программу так, чтобы она компилировалась
и связывалась в С++.
const char *str = «hello»;
void *malloc( int );
char *strcpy( char *, const char * );
int printf( const char *, … );
int exit( int );
int strlen( const char * );
int main()
{ /* программа на языке С */
char* s = malloc( strlen(str)+l );
strcpy( s, str );
printf( «%s, worldn», s );
exit( 0 );
}
7.8. Функция main(): разбор параметров командной строки
При запуске программы мы, как правило, передаем ей информацию в командной строке.
Например, можно написать
prog -d -o of lie dataO
Фактические параметры являются аргументами функции main() и могут быть получены
из массива C-строк с именем argv; мы покажем, как их использовать.
Во всех предыдущих примерах определение main() содержало пустой список:
int main() { ... }
Развернутая сигнатура main() позволяет получить доступ к параметрам, которые
были заданы пользователем в командной строке:
int main( int argc, char *argv[] ){...}
argc содержит их количество, а argv – C-строки, представляющие собой отдельные
значения (в командной строке они разделяются пробелами). Скажем, при запуске
команды
prog -d -o ofile data0
argc получает значение 5, а argv включает следующие строки:
argv[ 0 ] = "prog";
argv[ 1 ] = "-d";
argv[ 2 ] = "-o";
argv[ 3 ] = "ofile";
argv[ 4 ] = "dataO";
В argv[0] всегда входит имя команды (программы). Элементы с индексами от 1
до argc-1 служат параметрами.
Посмотрим, как можно извлечь и использовать значения, помещенные в argv. Пусть
программа из нашего примера вызывается таким образом:
prog [-d] [-h] [-v]
[-o output_file] [-l limit_value]
file_name
[ file_name [file_name [ ... ]]]
Параметры в квадратных скобках являются необязательными. Вот, например, запуск
программы с их минимальным количеством – одним лишь именем файла:
prog chap1.doc
Но можно запускать и так:
prog -l 1024 -o chap1-2.out chapl.doc chap2.doc
prog d chap3.doc
prog -l 512 -d chap4.doc
При разборе параметров командной строки выполняются следующие основные шаги:
- По очереди извлечь каждый параметр из argv. Мы используем для этого цикл
for с начальным индексом 1 (пропуская, таким образом, имя программы):for ( int ix = 1; ix < argc; ++ix ) {
char *pchar = argv[ ix ];
// ...
} - Определить тип параметра. Если строка начинается с дефиса (-), это одна
из опций { h, d, v, l, o}. В противном случае это может быть либо значение,
ассоциированное с опцией (максимальный размер для -l, имя выходного файла
для -o), либо имя входного файла. Чтобы определить, начинается ли строка с
дефиса, используем инструкцию switch:switch ( pchar[ 0 ] ) {
case '-': {
// -h, -d, -v, -l, -o
}
default: {
// обработаем максимальный размер для опции -1
// имя выходного файла для -o
// имена входных файлов ...
}
}
Реализуем обработку двух случаев пункта 2.
Если строка начинается с дефиса, мы используем switch по следующему символу
для определения конкретной опции. Вот общая схема этой части программы:
case '-': {
switch( pchar[ 1 ] )
{
case 'd':
// обработка опции debug
break;
case 'v':
// обработка опции version
break;
case 'h':
// обработка опции help
break;
case 'o':
// приготовимся обработать выходной файл
break;
case 'l':
// приготовимся обработать макс.размер
break;
default:
// неопознанная опция:
// сообщить об ошибке и завершить выполнение
}
}
Опция -d задает необходимость отладки. Ее обработка заключается в присваивании
переменной с объявлением
bool debug_on = false;
значения true:
case 'd':
debug_on = true;
break;
В нашу программу может входить код следующего вида:
if ( debug_on )
display_state_elements( obj );
Опция -v выводит номер версии программы и завершает исполнение:
case 'v':
cout << program_name << "::"
<< program_version << endl;
return 0;
Опция -h запрашивает информацию о синтаксисе запуска и завершает исполнение.
Вывод сообщения и выход из программы выполняется функцией usage():
case 'h':
// break не нужен: usage() вызывает exit()
usage();
Опция -o сигнализирует о том, что следующая строка содержит имя выходного файла.
Аналогично опция -l говорит, что за ней указан максимальный размер. Как нам
обработать эти ситуации?
Если в строке параметра нет дефиса, возможны три варианта: параметр содержит
имя выходного файла, максимальный размер или имя входного файла. Чтобы различать
эти случаи, присвоим true переменным, отражающим внутреннее состояние:
// если ofi1e_on==true,
// следующий параметр - имя выходного файла
bool ofi1e_on = false;
// если ofi1e_on==true,
// следующий параметр - максимальный размер
bool limit_on = false;
Вот обработка опций -l и -o в нашей инструкции switch:
case 'l':
limit_on = true;
break;
case 'o':
ofile_on = true;
break;
Встретив строку, не начинающуюся с дефиса, мы с помощью переменных состояния
можем узнать ее содержание:
// обработаем максимальный размер для опции -1
// имя выходного файла для -o
// имена входных файлов ...
default: {
// ofile_on включена, если -o встречалась
if ( ofile_on ) {
// обработаем имя выходного файла
// выключим ofile_on
}
else if ( limit_on ) { // если -l встречалась
// обработаем максимальный размер
// выключим limit_on
} else {
// обработаем имя входного файла
}
}
Если аргумент является именем выходного файла, сохраним это имя и выключим
ofile_on:
if ( ofile_on ) {
ofile_on = false;
ofile = pchar;
}
Если аргумент задает максимальный размер, мы должны преобразовать строку встроенного
типа в представляемое ею число. Сделаем это с помощью стандартной функции atoi(),
которая принимает строку в качестве аргумента и возвращает int (также существует
функция atof(), возвращающая double). Для использования atoi() включим заголовочный
файл ctype.h. Нужно проверить, что значение максимального размера неотрицательно
и выключить limit_on:
// int limit;
else
if ( limit_on ) {
limit_on = false;
limit = atoi( pchar );
if ( limit < 0 ) {
cerr << program_name << "::"
<< program_version << " : error: "
<< "negative value for limit.nn";
usage( -2 );
}
}
Если обе переменных состояния равны false, у нас есть имя входного файла. Сохраним
его в векторе строк:
else
file_names.push_back( string( pchar ));
При обработке параметров командной строки важен способ реакции на неверные опции.
Мы решили, что задание отрицательной величины в качестве максимального размера
будет фатальной ошибкой. Это приемлемо или нет в зависимости от ситуации. Также
можно распознать эту ситуацию как ошибочную, выдать предупреждение и использовать
ноль или какое-либо другое значение по умолчанию.
Слабость нашей реализации становится понятной, если пользователь небрежно относится
к пробелам, разделяющим параметры. Скажем, ни одна из следующих двух строк не
будет обработана:
prog — d dataOl
prog -oout_file dataOl
(Оба случая мы оставим для упражнений в конце раздела.)
Вот полный текст нашей программы. (Мы добавили инструкции печати для трассировки
выполнения.)
#include < iostream>
#include < string>
#include < vector>
#include < ctype.h>
const char *const program_name = "comline";
const char *const program_version = "version 0.01 (08/07/97)";
inline void usage( int exit_value = 0 )
{
// печатает отформатированное сообщение о порядке вызова
// и завершает программу с кодом exit_value ...
cerr << "порядок вызова:n"
<< program_name << " "
<< "[-d] [-h] [-v] nt"
<< "[-o output_file] [-l limit] nt"
<< "file_nament[file_name [file_name [ ... ]]]nn"
<< "где [] указывает на необязательность опции:nnt"
<< "-h: справка.ntt"
<< "печать этого сообщения и выходnnt"
<< "-v: версия.ntt"
<< "печать информации о версии программы и выходnnt"
<< "-d: отладка.ntt включает отладочную печатьnnt"
<< "-l limitntt"
<< "limit должен быть неотрицательным целым числомnnt"
<< "-o ofilentt"
<< "файл, в который выводится результатntt"
<< "по умолчанию результат записывается на стандартный выводnn"
<< "file_namentt"
<< "имя подлежащего обработке файлаntt"
<< "должно быть задано хотя бы одно имя --ntt"
<< "но максимальное число не ограниченоnn"
<< "примеры:ntt"
<< "$command chapter7.docntt"
<< "$command -d -l 1024 -o test_7_8 "
<< "chapter7.doc chapter8.docnn";
exit( exit_value );
}
int main( int argc, char* argv[] )
{
bool debug_on = false;
bool ofile_on = false;
bool limit_on = false;
int limit = -1;
string ofile;
vector file_names;
cout << "демонстрация обработки параметров в командной строке:n"
<< "argc: " << argc << endl;
for ( int ix = 1; ix < argc; ++ix )
{
cout << "argv[ " << ix << " ]: "
<< argv[ ix ] << endl;
char *pchar = argv[ ix ];
switch ( pchar[ 0 ] )
{
case '-':
{
cout << "встретился '-'n";
switch( pchar[ 1 ] )
{
case 'd':
cout << "встретилась -d: "
<< "отладочная печать включенаn";
debug_on = true;
break;
case 'v':
cout << "встретилась -v: "
<< "выводится информация о версииn";
cout << program_name
<< " :: "
<< program_version
<< endl;
return 0;
case 'h':
cout << "встретилась -h: "
<< "справкаn";
// break не нужен: usage() завершает программу
usage();
case 'o':
cout << "встретилась -o: выходной файлn";
ofile_on = true;
break;
case 'l':
cout << "встретилась -l: "
<< "ограничение ресурсаn";
limit_on = true;
break;
default:
cerr << program_name
<< " : ошибка : "
<< "неопознанная опция: - "
<< pchar << "nn";
// break не нужен: usage() завершает программу
usage( -1 );
}
break;
}
default: // либо имя файла
cout << "default: параметр без дефиса: "
<< pchar << endl;
if ( ofile_on ) {
ofile_on = false;
ofile = pchar;
}
else
if ( limit_on ) {
limit_on = false;
limit = atoi( pchar );
if ( limit < 0 ) {
cerr << program_name
<< " : ошибка : "
<< "отрицательное значение limit.nn";
usage( -2 );
}
}
else file_names.push_back( string( pchar ));
break;
}
}
if ( file_names.empty() ) {
cerr << program_name
<< " : ошибка : "
<< "не задан ни один входной файл.nn";
usage( -3 );
}
if ( limit != -1 )
cout << "Заданное пользователем значение limit: "
<< limit << endl;
if ( ! ofile.empty() )
cout << "Заданный пользователем выходной файл: "
<< ofile << endl;
cout << (file_names.size() == 1 ? "Файл, " : "Файлы, ")
<< "подлежащий(е) обработке:n";
for ( int inx = 0; inx < file_names.size(); ++inx )
cout << "t" << file_names[ inx ] << endl;
}
a.out -d -l 1024 -o test_7_8 chapter7.doc chapters.doc
Вот трассировка обработки параметров командной строки:
демонстрация обработки параметров в командной строке:
argc: 8
argv[ 1 ]: -d
встретился '-'
встретилась -d: отладочная печать включена
argv[ 2 ]: -l
встретился '-'
встретилась -l: ограничение ресурса
argv[ 3 ]: 1024
default: параметр без дефиса: 1024
argv[ 4 ]: -o
встретился '-'
встретилась -o: выходной файл
argv[ 5 ]: test_7_8
default: параметр без дефиса: test_7_8
argv[ 6 ]: chapter7.doc
default: параметр без дефиса: chapter7.doc
argv[ 7 ]: chapter8.doc
default: параметр без дефиса: chapter8.doc
Заданное пользователем значение limit: 1024
Заданный пользователем выходной файл: test_7_8
Файлы, подлежащий(е) обработке:
chapter7.doc
chapter8.doc
7.8.1. Класс для обработки параметров командной строки
Чтобы не перегружать функцию main() деталями, касающимися обработки параметров
командной строки, лучше отделить этот фрагмент. Можно написать для этого функцию.
Например:
extern int parse_options( int arg_count, char *arg_vector );
int main( int argc, char *argv[] ) {
// ...
int option_status;
option_status = parse_options( argc, argv );
// ...
}
Как вернуть несколько значений? Обычно для этого используются глобальные объекты,
которые не передаются ни в функцию для их обработки, ни обратно. Альтернативной
стратегией является инкапсуляция обработки параметров командной строки в класс.
Данные-члены класса представляют собой параметры, заданные пользователем в командной
строке. Набор открытых встроенных функций-членов позволяет получать их значения.
Конструктор инициализирует параметры значениями по умолчанию. Функция-член получает
argc и argv в качестве аргументов и обрабатывает их:
#include
#include
class CommandOpt {
public:
CommandOpt() : _limit( -1 ), _debug_on( false ) {}
int parse_options( int argc, char *argv[] );
string out_file() { return _out_file; }
bool debug_on() { return _debug_on; }
int files() { return _file_names.size(); }
string& operator[]( int ix );
private:
inline void usage( int exit_value = 0 );
bool _debug_on;
int _limit;
string _out_file;
vector _file_names;
static const char *const program_name;
static const char *const program_version;
};
Так выглядит модифицированная функция main():
#include "CommandOpt.h"
int main( int argc, char "argv[] ) {
// ...
CommandOpt com_opt;
int option_status;
opttion_status = com_opt. parse_options (argc, argv);
// ...
}
Упражнение 7.15
Добавьте обработку опций -t (включение таймера) и -b (задание размера буфера
bufsize). Не забудьте обновить usage(). Например:
prog -t -b 512 dataO
Упражнение 7.16
Наша реализация не обрабатывает случая, когда между опцией и ассоциированным
с ней значением нет пробела. Модифицируйте программу для поддержки такой обработки.
Упражнение 7.17
Наша реализация не может различить лишний пробел между дефисом и опцией:
prog — d dataO
Модифицируйте программу так, чтобы она распознавала подобную ошибку и сообщала
о ней.
Упражнение 7.18
В нашей программе не предусмотрен случай, когда опции -l или -o задаются несколько
раз. Реализуйте такую возможность. Какова должна быть стратегия при разрешении
конфликта?
Упражнение 7.19
В нашей реализации задание неизвестной опции приводит к фатальной ошибке. Как
вы думаете, это оправдано? Предложите другое поведение.
Упражнение 7.20
Добавьте поддержку опций, начинающихся со знака плюс (+), обеспечив обработку
+s и +pt, а также +sp и +ps. Предположим, что +s включает строгую проверку синтаксиса,
а +p допускает использование устаревших конструкций. Например:
prog +s +p -d -b 1024 dataO
7.9. Указатели на функции
Предположим, что нам нужно написать функцию сортировки, вызов которой выглядит
так:
sort( start, end, compare );
где start и end являются указателями на элементы массива строк. Функция sort()
сортирует элементы между start и end, а аргумент compare задает операцию сравнения
двух строк этого массива.
Какую реализацию выбрать для compare? Мы можем сортировать строки лексикографически,
т.е. в том порядке, в котором слова располагаются в словаре, или по длине –
более короткие идут раньше более длинных. Нам нужен механизм для задания альтернативных
операций сравнения. (Заметим, что в главе 12 описан алгоритм sort() и другие
обобщенные алгоритмы из стандартной библиотеки С++. В этом разделе мы покажем
свою собственную версию sort() как пример употребления указателей на функции.
Наша функция будет упрощенным вариантом стандартного алгоритма.)
Один из способов удовлетворить наши потребности – использовать в качестве третьего
аргумента compare указатель на функцию, применяемую для сравнения.
Для того чтобы упростить использование функции sort(), не жертвуя гибкостью,
можно задать операцию сравнению по умолчанию, подходящую для большинства случаев.
Предположим, что чаще всего нам требуется лексикографическая сортировка, поэтому
в качестве такой операции возьмем функцию compare() для строк (эта функция впервые
встретилась в разделе 6.10).
7.9.1. Тип указателя на функцию
Как объявить указатель на функцию? Как выглядит формальный параметр, когда
фактическим аргументом является такой указатель? Вот определение функции lexicoCompare(),
которая сравнивает две строки лексикографически:
#include <string>
int lexicoCompare( const string &sl, const string &s2 ) {
return sl.compare(s2);
}
Если все символы строк s1 и s2 равны, lexicoCompare() вернет 0, в противном
случае – отрицательное число, если s1 меньше чем s2, и положительное, если s1
больше s2.
Имя функции не входит в ее сигнатуру – она определяется только типом возвращаемого
значения и списком параметров. Указатель на lexicoCompare() должен адресовать
функцию с той же сигнатурой. Попробуем написать так:
int *pf( const string &, const string & ) ;
// нет, не совсем так
Эта инструкция почти правильна. Проблема в том, что компилятор интерпретирует
ее как объявление функции с именем pf, которая возвращает указатель типа int*.
Список параметров правилен, но тип возвращаемого значения не тот. Оператор разыменования
(*) ассоциируется с данным типом (int в нашем случае), а не с pf. Чтобы исправить
положение, нужно использовать скобки:
int (*pf)( const string &, const string & ) ;
// правильно
pf объявлен как указатель на функцию с двумя параметрами, возвращающую значение
типа int, т.е. такую, как lexicoCompare().
pf способен адресовать и приведенную ниже функцию, поскольку ее сигнатура совпадает
с типом lexicoCompare():
int sizeCompare( const string &sl, const string &s2 );
Функции calc() и gcd()другого типа, поэтому pf не может указывать на них:
int calc( int , int );
int gcd( int , int );
Указатель, который адресует эти две функции, определяется так:
int (*pfi)( int, int );
Многоточие является частью сигнатуры функции. Если у двух функций списки параметров
отличаются только тем, что в конце одного из них стоит многоточие, то считается,
что функции различны. Таковы же и типы указателей.
int printf( const char*, ... );
int strlen( const char* );
int (*pfce)( const char*, ... ); // может указывать на printf()
int (*pfc)( const char* ); // может указывать на strlen()
Типов функций столько, сколько комбинаций типов возвращаемых значений и списков
параметров.
7.9.2. Инициализация и присваивание
Вспомним, что имя массива без указания индекса элемента интерпретируется как
адрес первого элемента. Аналогично имя функции без следующих за ним скобок интерпретируется
как указатель на функцию. Например, при вычислении выражения
lexicoCompare;
получается указатель типа
int (*)( const string &, const string & );
Применение оператора взятия адреса к имени функции также дает указатель того
же типа, например lexicoCompare и &lexicoCompare. Указатель на функцию инициализируется
следующим образом:
int (*pfi)( const string &, const string & ) = lexicoCompare;
int (*pfi2)( const string &, const string & ) = &lexicoCompare;
Ему можно присвоить значение:
pfi = lexicoCompare;
pfi2 = pfi;
Инициализация и присваивание корректны только тогда, когда список параметров
и тип значения, которое возвращает функция, адресованная указателем в левой
части операции присваивания, в точности соответствуют списку параметров и типу
значения, возвращаемого функцией или указателем в правой части. В противном
случае выдается сообщение об ошибке компиляции. Никаких неявных преобразований
типов для указателей на функции не производится. Например:
int calc( int, int );
int (*pfi2s)( const string &, const string & ) = 0;
int (*pfi2i)( int, int ) = 0;
int main() {
pfi2i = calc; // правильно
pri2s = calc; // ошибка: несовпадение типов
pfi2s = pfi2i; // ошибка: несовпадение типов
return 0;
}
Такой указатель можно инициализировать нулем или присвоить ему нулевое значение,
в этом случае он не адресует функцию.
7.9.3. Вызов
Указатель на функцию применяется для вызова функции, которую он адресует. Включать
оператор разыменования при этом необязательно. И прямой вызов функции по имени,
и косвенный вызов по указателю записываются одинаково:
#include
int min( int*, int );
int (*pf)( int*, int ) = min;
const int iaSize = 5;
int ia[ iaSize ] = { 7, 4, 9, 2, 5 };
int main() {
cout << "Прямой вызов: min: "
<< min( ia, iaSize ) << endl;
cout << "Косвенный вызов: min: "
<< pf( ia, iaSize ) << endl;
return 0;
}
int min( int* ia, int sz ) {
int minVal = ia[ 0 ];
for ( int ix = 1; ix < sz; ++ix )
if ( minVal > ia[ ix ] )
minVal = ia[ ix ];
return minVal;
}
Вызов
pf( ia, iaSize );
может быть записан также и с использованием явного синтаксиса указателя:
(*pf)( ia, iaSize );
Результат в обоих случаях одинаковый, но вторая форма говорит читателю, что
вызов осуществляется через указатель на функцию.
Конечно, если такой указатель имеет нулевое значение, то любая форма вызова
приведет к ошибке во время выполнения. Использовать можно только те указатели,
которые адресуют какую-либо функцию или были проинициализированы таким значением.
7.9.4. Массивы указателей на функции
Можно объявить массив указателей на функции. Например:
int (*testCases[10])();
testCases – это массив из десяти элементов, каждый из которых является указателем
на функцию, возвращающую значение типа int и не имеющую параметров.
Подобные объявления трудно читать, поскольку не сразу видно, с какой частью
ассоциируется тип функции.
В этом случае помогает использование имен, определенных с помощью директивы
typedef:
// typedef делает объявление более понятным
typedef int (*PFV)(); // typedef для указателя на функцию
PFV testCases[10];
Данное объявление эквивалентно предыдущему.
Вызов функций, адресуемых элементами массива testCases, выглядит следующим образом:
const int size = 10;
PFV testCases[size];
int testResults[size];
void runtests() {
for ( int i = 0; i < size; ++i )
// вызов через элемент массива
testResults[ i ] = testCases[ i ]();
}
Массив указателей на функции может быть инициализирован списком, каждый элемент
которого является функцией. Например:
int lexicoCompare( const string &, const string & );
int sizeCompare( const string &, const string & );
typedef int ( *PFI2S )( const string &, const string & );
PFI2S compareFuncs[2] =
{
lexicoCompare,
sizeCompare
};
Можно объявить и указатель на compareFuncs, его типом будет “указатель на массив
указателей на функции”:
PFI2S (*pfCompare)[2] = compareFuncs;
Это объявление раскладывается на составные части следующим образом:
(*pfCompare)
Оператор разыменования говорит, что pfCompare является указателем. [2] сообщает
о количестве элементов массива:
(*pfCompare) [2]
PFI2S – имя, определенное с помощью директивы typedef, называет тип элементов.
Это “указатель на функцию, возвращающую int и имеющую два параметра типа const
string &”. Тип элемента массива тот же, что и выражения &lexicoCompare.
Такой тип имеет и первый элемент массива compareFuncs, который может быть получен
с помощью любого из выражений:
compareFunc[ 0 ];
(*pfCompare)[ 0 ];
Чтобы вызвать функцию lexicoCompare через pfCompare, нужно написать одну из
следующих инструкций:
// эквивалентные вызовы
pfCompare [ 0 ]( string1, string2 ); // сокращенная форма
((*pfCompare)[ 0 ])( string1, string2 ); // явная форма
7.9.5. Параметры и тип возврата
Вернемся к задаче, сформулированной в начале данного раздела. Как использовать
указатели на функции для сортировки элементов? Мы можем передать в алгоритм
сортировки указатель на функцию, которая выполняет сравнение:
int sort( string*, string*,
int (*)( const string &, const string & ) );
И в этом случае директива typedef помогает сделать объявление sort() более
понятным:
// Использование директивы typedef делает
// объявление sort() более понятным
typedef int ( *PFI2S )( const string &, const string & );
int sort( string*, string*, PFI2S );
Поскольку в большинстве случаев употребляется функция lexicoCompare, можно
использовать значение параметра по умолчанию:
// значение по умолчанию для третьего параметра
int lexicoCompare( const string &, const string & );
int sort( string*, string*, PFI2S = lexicoCompare );
Определение sort() выглядит следующим образом:
1 void sort( string *sl, string *s2,
2 PFI2S compare = lexicoCompare )
3 {
4 // условие окончания рекурсии
5 if ( si < s2 ) {
6 string elem = *s1;
7 string *1ow = s1;
8 string *high = s2 + 1;
9
10 for (;;) {
11 while ( compare ( *++1ow, elem ) < 0 && low < s2) ;
12 while ( compare( elem, *--high ) < 0 && high > s1)
14 if ( low < high )
15 1ow->swap(*high);
16 else break;
17 } // end, for(;;)
18
19 s1->swap(*high);
20 sort( s1, high - 1 );
21 sort( high +1, s2 );
22 } // end, if ( si < s2 )
23 }
sort() реализует алгоритм быстрой сортировки Хоара (C.A.R.Hoare). Рассмотрим
ее определение детально. Она сортирует элементы массива от s1 до s2. Это рекурсивная
функция, которая вызывает сама себя для последовательно уменьшающихся подмассивов.
Рекурсия окончится тогда, когда s1 и s2 укажут на один и тот же элемент или
s1 будет располагаться после s2 (строка 5).
elem (строка 6) является разделяющим элементом. Все элементы, меньшие чем elem,
перемещаются влево от него, а большие – вправо. Теперь массив разбит на две
части. sort() рекурсивно вызывается для каждой из них (строки 20-21).
Цикл for(;;) проводит разделение (строки 10-17). На каждой итерации цикла индекс
low увеличивается до первого элемента, большего или равного elem (строка 11).
Аналогично high уменьшается до последнего элемента, меньшего или равного elem
(строка 12). Когда low становится равным или большим high, мы выходим из цикла,
в противном случае нужно поменять местами значения элементов и начать новую
итерацию (строки 14-16). Хотя элементы разделены, elem все еще остается первым
в массиве. swap() в строке 19 ставит его на место до рекурсивного вызова sort()
для двух частей массива.
Сравнение производится вызовом функции, на которую указывает compare (строки
11-12). Чтобы поменять элементы массива местами, используется операция swap()
с аргументами типа string, представленная в разделе 6.11.
Вот как выглядит main(), в которой применяется наша функция сортировки:
#include < iostream>
#include < string>
// это должно бы находиться в заголовочном файле
int lexicoCompare( const string &, const string & );
int sizeCompare( const string &, const string & );
typedef int (*PFI)( const string &, const string & );
void sort( string *, string *, PFI=lexicoCompare );
string as[10] = { "a", "light", "drizzle", "was", "falling",
"when", "they", "left", "the", "museum" };
int main() {
// вызов sort() с значением по умолчанию параметра compare
sort( as, as + sizeof(as)/sizeof(as[0]) - 1 );
// выводим результат сортировки
for ( int i = 0; i < sizeof(as)/sizeof(as[0]); ++i )
cout << as[ i ].c_str() << "nt";
}
Результат работы программы:
"a"
"drizzle"
"falling"
"left"
"light"
"museum"
"the"
"they"
"was"
"when"
Параметр функции автоматически приводится к типу указателя на функцию:
// typedef представляет собой тип функции
typedef int functype( const string &, const string & );
void sort( string *, string *, functype );
sort() рассматривается компилятором как объявленная в виде
void sort( string *, string *,
int (*)( const string &, const string & ) );
Два этих объявления sort() эквивалентны.
Заметим, что, помимо использования в качестве параметра, указатель на функцию
может быть еще и типом возвращаемого значения. Например:
int (*ff( int ))( int*, int );
ff() объявляется как функция, имеющая один параметр типа int и возвращающая указатель на функцию типа
int (*)( int*, int );
И здесь использование директивы typedef делает объявление понятнее. Объявив
PF с помощью typedef, мы видим, что ff() возвращает указатель на функцию:
// Использование директивы typedef делает
// объявления более понятными
typedef int (*PF)( int*, int );
PF ff( int );
Типом возвращаемого значения функции не может быть тип функции. В этом случае
выдается ошибка компиляции. Например, нельзя объявить ff() таким образом:
// typedef представляет собой тип функции
typedef int func( int*, int );
func ff( int ); // ошибка: тип возврата ff() — функция
7.9.6. Указатели на функции, объявленные как extern «C»
Можно объявлять указатели на функции, написанные на других языках программирования.
Это делается с помощью директивы связывания. Например, указатель pf ссылается
на С-функцию:
extern "C" void (*pf)(int);
Через pf вызывается функция, написанная на языке С.
extern "C" void exit(int);
// pf ссылается на C-функцию exit()
extern "C" void (*pf)(int) = exit;
int main() {
// ...
// вызов С-функции, а именно exit()
(*pf)(99);
}
Вспомним, что присваивание и инициализация указателя на функцию возможны лишь
тогда, когда тип в левой части оператора присваивания в точности соответствует
типу в правой его части. Следовательно, указатель на С-функцию не может адресовать
функцию С++ (и инициализация его таким адресом не допускается), и наоборот.
Подобная попытка вызывает ошибку компиляции:
void (*pfl)(int);
extern "C" void (*pf2)(int);
int main() {
pfl = pf2; // ошибка: pfl и pf2 имеют разные типы
// ...
}
Отметим, что в некоторых реализациях С++ характеристики указателей на функции
С и С++ одинаковы. Отдельные компиляторы могут допустить подобное присваивание,
рассматривая это как расширение языка.
Если директива связывания применяется к объявлению, она затрагивает все функции,
участвующие в данном объявлении.
В следующем примере параметр pfParm также служит указателем на С-функцию. Директива
связывания применяется к объявлению функции, к которой этот параметр относится:
// pfParm - указатель на С-функцию
extern "C" void f1( void(*pfParm)(int) );
Следовательно, f1() является С-функцией с одним параметром – указателем на
С-функцию. Значит, передаваемый ей аргумент должен быть либо такой же функцией,
либо указателем на нее, поскольку считается, что указатели на функции, написанные
на разных языках, имеют разные типы. (Снова заметим, что в тех реализациях С++,
где указатели на функции С и С++ имеют одинаковые характеристики, компилятор
может поддерживать расширение языка, позволяющее не различать эти два типа указателей.)
Коль скоро директива связывания относится ко всем функциям в объявлении, то
как же объявить функцию С++, имеющую в качестве параметра указатель на С-функцию?
С помощью директивы typedef. Например:
// FC представляет собой тип:
// С-функция с параметром типа int, не возвращающая никакого значения
extern "C" typedef void FC( int );
// f2() - C++ функция с параметром -
// указателем на С-функцию
void f2( FC *pfParm );
Упражнение 7.21
В разделе 7.5 приводится определение функции factorial(). Напишите объявление
указателя на нее. Вызовите функцию через этот указатель для вычисления факториала
11.
Упражнение 7.22
Каковы типы следующих объявлений:
(a) int (*mpf)(vector<int>&);
(b) void (*apf[20])(doub1e);
(c) void (*(*papf)[2])(int);
Как сделать эти объявления более понятными, используя директивы typedef?
Упражнение 7.23
Вот функции из библиотеки С, определенные в заголовочном файле <cmath>:
double abs(double);
double sin(double);
double cos(double);
double sqrt(double);
Как бы вы объявили массив указателей на С-функции и инициализировали его этими
четырьмя функциями? Напишите main(), которая вызывает sqrt() с аргументом 97.9
через элемент массива.
Упражнение 7.24
Вернемся к примеру sort(). Напишите определение функции
int sizeCompare( const string &, const string & );
Если передаваемые в качестве параметров строки имеют одинаковую длину, то sizeCompare()
возвращает 0; если первая строка короче второй, то отрицательное число, а если
длиннее, то положительное. Напоминаем, что длина строки возвращается операцией
size() класса string. Измените main() для вызова sort(), передав в качестве
третьего аргумента указатель на sizeCompare().


