The message says that you try to assign to an expression which is not an lvalue. For built-in types, you can only assign to lvalues (that’s where the name comes from: lvalue = value that can be on the left hand side of the assignment operator, while rvalue = value that must be on the right hand side of the assignment operator).
So what is an lvalue or an rvalue? Consider the following code:
int a;
a = 3;
In this assignment a
is an lvalue (if it weren’t, the compiler would complain). That is, the expression a
refers to an object which can be modified. On the other hand, 3
is an rvalue, that is, basically a value. Of course you cannot assign to 3
; the compiler would complain about the statement 3=a;
with exactly the same message you got in your code.
So as a first approximation, an lvalue designates an object, while an rvalue designates a value. Note that this is also true for assignment of the form
a = b;
where b
also is a variable. What happens here is the so-called lvalue to rvalue conversion: What is assigned is not the object b
, but its current value.
Now consider the following case:
int f();
f() = 3;
Here you might argue that the function f
does return an object (if you use some user-defined type, you even can see its construction/destruction). But the compiler still complains with the message you got. Why?
Well, even if you consider f
to return an object, it is a temporary object which will go away immediately. So it does not make much sense to assign a value because you cannot do anything with it anyway afterwards.
Therefore here’s the second rule:
Whenever there’s an expression which produces a temporary object, C++ defines that expression as rvalue.
And now we come to the definition of MyVector::at()
which you did not show, but which, according to the error message, probably looks similar to this:
template<typename T>
T MyVector<T>::at(int i)
{
return data[i];
}
This has essentially the same form as f
above, as it also returns a T
(an employee*
in your case). This is why the compiler complains.
And that complaint is helpful: Even if the compiler wouldn’t complain, the code would not dio what you almost certainly intended. The return
statement returns a copy of the object data[i]
. Thus if the statement payment.at(i)=NULL;
had compiled, what would actually happen would be the following:
- The internal object
data[i]
(or however you called it in your code) is copied and the temporary copy returned. - The statement assigned that temporary copy, but leaves the original object in
MyVector
unchanged. - The temporary copy gets destructed, leaving no trace of your assignment.
This is almost certainly not what you wanted. You wanted to change the internal object. To do so, you have to return a reference to that object. A reference refers to the object it was initialized with instead of making a copy. Correspondingly, a reference, even when returned, is an lvalue (since C++11 there’s a second type of reference which behaves differently, but we don’t need to care about that here). Your corrected function then reads
template<typename T>
T& MyVector<T>::at(int i)
{
return data[i];
}
and with that definition, payment.at(i)=NULL;
not only compiles, but actually does what you want: Change the internally stored i
-th pointer in payment
to NULL
.
- Remove From My Forums
-
Question
-
Hi All,
class Bucket { public: Bucket() {count=0; label=-1; }; public: float label, count; void displayData(); float getLabel(); }; const int MAX=2000;
Bucket ang[MAX];ifstream inlabel(«Labels of Buckets.txt»);
k=0;
float labelset;
while(inlabel >> labelset && k<1023)
{
ang[k].getLabel() = labelset;
cout << ang[k].getLabel() << endl;
k++;
}inlabel.close(); float Bucket::getLabel() { return label; } Why it shows»error C2106: ‘=’ : left operand must be l-value»?? I don’t really understand what it means, sicne my label is defined as a float already.
Thanks a lot!
Thank you for replying! I love this forum!
-
Edited by
Saturday, August 9, 2008 12:05 AM
typo
-
Edited by
Answers
-
The easy fix is to have getLabel return a float& instead of a float, but then the name getLabel is a misnomer. So a better option would be to have separate member functions for getting and setting label:
class Bucket { public: Bucket() : label_(-1.0f), count_(0.0f) { } float getLabel() const { return label_; } void setLabel(float l) { label_ = l; } private: float label_; float count_; }; This is common enough that it is normal to elide the ‘get’ and ‘set’ part of the names, and distinguish between the methods only by their parameters and return types:
class Bucket { public: Bucket() : label_(-1.0f), count_(0.0f) { } float label() const { return label_; } void label(float l) { label_ = l; } private: float label_; float count_; }; -
Marked as answer by
Yan-Fei Wei
Wednesday, August 13, 2008 6:50 AM
-
Marked as answer by
- Remove From My Forums
-
Question
-
I have trouble finding my mistakes. I used visual studio to practice C language. I am not sure if C language works perfectly fine here, for I copy some C language program from a textbook and it doesn’t work.
Like the one below, it is from a textbook, but the runing result show that «error C2106: ‘=’ : left operand must be l-value
1>» and the mistake is in sentence «if (c == ‘ ‘||c == ‘n’||c= ‘t’)». Is anyone can help me fix the problem? Thank you.#include<stdio.h>
#define IN 1
#define OUT 0main()
{
int c, nl, nw, nc, state;state = OUT;
nl = nw = nc = 0;
while ((c = getchar()) !=EOF) {
++nc;
if (c == ‘n’)
++nl;
if (c == ‘ ‘||c == ‘n’||c= ‘t’)
state = OUT;
else if (state == OUT) {
state = IN;
++nw;
}
}
printf(«%d %d %dn», nl, nw, nc);
}-
Moved by
Sunday, September 1, 2013 9:10 AM
-
Moved by
Answers
-
Even though it’s not SB, that 1 is easy!
if (c == ‘ ‘||c == ‘n’||c= ‘t’)
At the end of that expression, you’re assigning the value ‘t’ to variable c, instead of comparing variable c to value ‘t’.
That’s gonna happen every time c is neither ‘ ‘ nor ‘n’. In short, almost every time!
The way C works is that any value not 0 is true. So, by making c = ‘t’, the whole condition’s gonna be evaluated as true too.
That also means that state is gonna be = to OUT, b/c the condition above is always true, no matter what!
Click on «Propose As Answer» if some post solves your problem or «Vote As Helpful» if some post has been useful to you! (^_^)
-
Edited by
GoToLoop
Sunday, September 1, 2013 2:23 AM
… -
Marked as answer by
BruceX
Sunday, September 1, 2013 3:03 AM
-
Edited by
Alonka 0 / 0 / 0 Регистрация: 17.04.2016 Сообщений: 18 |
||||||||
1 |
||||||||
24.04.2016, 17:05. Показов 5153. Ответов 31 Метки c++ (Все метки)
Добрый день! Пишу программу управления колледжом. И столкнулась с этой ошибкой. У меня есть класс Department который имеет массив указателей на Course:
И в нем я пытаюсь перегрузить оператор += который добавляет Course в Department:
Ошибка появляется тогда когда я хочу получить указатель на курсы и создать массив.
__________________
0 |
7275 / 6220 / 2833 Регистрация: 14.04.2014 Сообщений: 26,871 |
|
25.04.2016, 14:00 |
2 |
get_p_course() возвращает значение указателя. Естественно, ему нельзя присвоить.
1 |
Alonka 0 / 0 / 0 Регистрация: 17.04.2016 Сообщений: 18 |
||||||||||||||||
25.04.2016, 16:39 [ТС] |
3 |
|||||||||||||||
+= friend потому что я использую его в другом классе. В классе College. Добавлено через 8 минут Error C2803 ‘operator +=’ must have at least one formal parameter of class type Добавлено через 2 минуты
Добавлено через 14 минут
И тогда в классе College чтоб записать Course в Department нужно так?????
Добавлено через 49 минут
0 |
7275 / 6220 / 2833 Регистрация: 14.04.2014 Сообщений: 26,871 |
|
25.04.2016, 18:08 |
4 |
+= friend потому что я использую его в другом классе. И что? Оно и без friend должно работать. Ошибки эти, наверное, из-за того, что не видит этот Course как тип. А добавление в массив указателей потребует выделять память с учётом добавления, потом всё копировать, удалять старое и заменять p_course. Сделать так, как у тебя, значит потерять все данные, ещё и утечка памяти будет.
1 |
Alonka 0 / 0 / 0 Регистрация: 17.04.2016 Сообщений: 18 |
||||
26.04.2016, 09:53 [ТС] |
5 |
|||
Спасибо большое! Разобралась вроде с operator +=:
Но все не пойму насчет этих синтаксических ошибок в Department.h. Почему он не видит Course как тип?
0 |
7275 / 6220 / 2833 Регистрация: 14.04.2014 Сообщений: 26,871 |
|
26.04.2016, 10:01 |
6 |
Из этого фрагмента не ясно.
0 |
0 / 0 / 0 Регистрация: 17.04.2016 Сообщений: 18 |
|
26.04.2016, 10:09 [ТС] |
7 |
Из этого фрагмента не ясно. Исправила буквально только что! Спасибо огромное за помощь!!!Буду делать дальше.Надеюсь поможешь если еще возникнут вопросы!! А насчет массивов указателей. Я делаю как создаю new копирую в temp добавляю еще один объект и потом обратно создаю new и копирую все туда. Ну и конечно delete. А другого способа я даже и не знаю.
0 |
7275 / 6220 / 2833 Регистрация: 14.04.2014 Сообщений: 26,871 |
|
26.04.2016, 10:41 |
8 |
Ну там не было ни копирования, ни освобождения, просто присваивалось.
1 |
Alonka 0 / 0 / 0 Регистрация: 17.04.2016 Сообщений: 18 |
||||||||||||||||||||
27.04.2016, 14:07 [ТС] |
9 |
|||||||||||||||||||
У меня возник один вопрос.
Добавлено через 20 минут
У меня возник один вопрос.
Короче перегрузила просто по другому сам оператор ввода:
Добавлено через 3 часа 14 минут
И потом перегружаю оператор -= кторый удаляет Student с Course и получаю ошибку access violation reading location:
0 |
7275 / 6220 / 2833 Регистрация: 14.04.2014 Сообщений: 26,871 |
|
27.04.2016, 15:11 |
10 |
А что такое p_student? Второй цикл идёт поверх первого, тогда зачем он? Учитывая, что здесь указатели, оператор [] вообще не применяется.
0 |
0 / 0 / 0 Регистрация: 17.04.2016 Сообщений: 18 |
|
27.04.2016, 18:23 [ТС] |
11 |
p_student это Student** p_student массив указателей который находиться в class Course.
0 |
7275 / 6220 / 2833 Регистрация: 14.04.2014 Сообщений: 26,871 |
|
27.04.2016, 18:50 |
12 |
А почему двумерный массив? Одномерного мало, что ли, для списка студентов? get_p_student() что возвращает? Выражение в 4-й строке сомнительно.
0 |
Alonka 0 / 0 / 0 Регистрация: 17.04.2016 Сообщений: 18 |
||||
27.04.2016, 18:55 [ТС] |
13 |
|||
Student** p_student это так по условию задания.
0 |
nmcf 7275 / 6220 / 2833 Регистрация: 14.04.2014 Сообщений: 26,871 |
||||
27.04.2016, 21:25 |
14 |
|||
Ты же эту функцию относительно Student вызываешь. Каждый Student хранит указатель на массив всех Student, что ли? Добавлено через 3 минуты
Один p_student у Course, это понятно. А у s он откуда? И здесь получается (Student** — Student*), разные указатели.
1 |
Alonka 0 / 0 / 0 Регистрация: 17.04.2016 Сообщений: 18 |
||||||||||||||||||||
28.04.2016, 09:45 [ТС] |
15 |
|||||||||||||||||||
Тоесть get_p_student() вызывается из Course.
А это
я хочу получить индекс по которому находиться Student которого я хочу удалить. Оператор -= получает объект s :
Мы вводим id_course и id_student и если такие существуют то мы должны удалить temp_student[k]. Добавлено через 17 минут
0 |
nmcf 7275 / 6220 / 2833 Регистрация: 14.04.2014 Сообщений: 26,871 |
||||
28.04.2016, 10:17 |
16 |
|||
В этом фрагменте get_p_student() вызывается для s, а s — Student. Так что он возвращает?
Ну хорошо, для чего это статическое поле? Я так и не пойму, как эта разность работает.
0 |
Alonka 0 / 0 / 0 Регистрация: 17.04.2016 Сообщений: 18 |
||||
28.04.2016, 10:18 [ТС] |
17 |
|||
Совсем забыла, и в Student.h есть
0 |
7275 / 6220 / 2833 Регистрация: 14.04.2014 Сообщений: 26,871 |
|
28.04.2016, 10:20 |
18 |
Я прочитал. Для чего это? Какое предназначение у статического поля?
0 |
Alonka 0 / 0 / 0 Регистрация: 17.04.2016 Сообщений: 18 |
||||
28.04.2016, 10:23 [ТС] |
19 |
|||
Что каждый объект Student возвращает указатель на себя. И везде используются не объекты а только указатели на них.
0 |
7275 / 6220 / 2833 Регистрация: 14.04.2014 Сообщений: 26,871 |
|
28.04.2016, 10:28 |
20 |
Указатель на себя — this, а статическое поле будет общим для все объектов класса. Добавлено через 1 минуту
0 |
Once again, I come seeking help from those more knowledgeable than I. Anyway, I got most of the bugs worked out of this code, but I keep getting the error C2106: ‘=’ : left operand must be l-value message when I try to run this code. Of course I am getting it on my IF statements. If someone could point out my mistake, I would really appreciate it.
|
|
11 Years Ago
I am just starting to teach myself C++ and i have no coding experience so please bear with me on this if I ask you to explain your reasoning behind your response.
My code is:
// operating with varables
#include <iostream>
using namespace std;
//declarations
char STAR;
int PRIME = 71;
//begin main body
int main ()
{
int count, sum, ex, num;
sum = count + PRIME;
num = count * 1 + 2;
(sum + count = ex);
ex = sum * count;
count = 25.67;
cout << " Count = " << count << ", Sum = " << sum << ", Prime = " << PRIME << endl;
return 0;
}
Debug errors received:
Error 1 error C2106: '=' : left operand must be l-value (error on line in bold)
Warning 2 warning C4244: '=' : conversion from 'double' to 'int', possible loss of data
Let me know what you all think.
Edited
10 Years Ago
by Dani because:
Formatting fixed
Recommended Answers
How about:
ex=(sum+count);
?
Also the value 25.67 has a fractional part and an integer does not.
If you want the value to keep the .67, you will need to make the count a double (or other type of floating point number).The warning you …
Jump to Post
All 4 Replies
thines01
401
Postaholic
Team Colleague
Featured Poster
11 Years Ago
How about:
ex=(sum+count);
?
Also the value 25.67 has a fractional part and an integer does not.
If you want the value to keep the .67, you will need to make the count a double (or other type of floating point number).
The warning you got means the framework will convert that double to an integer and it will lose the fractional part.
Edited
11 Years Ago
by thines01 because:
clarity
11 Years Ago
Error 1 error C2106: ‘=’ : left operand must be l-value (error on line in bold)
Did you mean to put the value you get after summing sum and count into ex. If so, you should do like this.
ex = sum + count;
Warning 2 warning C4244: ‘=’ : conversion from ‘double’ to ‘int’, possible loss of data
count is declared as an int, which means it can hold values like 100, 101, 102… but not 101.43 or 3.14. But here you are trying to assign 25.67 to count. This is not an error, but eventually your count will ignore 0.67 and store only 25. So, if the 0.67 is important in your case you should use data types like double or float for count
MandrewP
60
Junior Poster in Training
11 Years Ago
I am just starting to teach myself C++ and i have no coding experience so please bear with me on this if I ask you to explain your reasoning behind your response.
My code is:
> // operating with varables
> #include <iostream>
> using namespace std;
>
> //declarations
> char STAR;
> int PRIME = 71;
>
> //begin main body
> int main ()
> {
> int count, sum, ex, num;
> sum = count + PRIME;
> num = count * 1 + 2;
> (sum + count = ex);
> ex = sum * count;
> count = 25.67;
> cout << " Count = " << count << ", Sum = " << sum << ", Prime = " << PRIME << endl;
> return 0;
>
> }
Debug errors received: Error 1 error C2106: '=' : left operand must be l-value (error on line in bold) Warning 2 warning C4244: '=' : conversion from 'double' to 'int', possible loss of data
Let me know what you all think.
Keep in mind that the «=» symbol is an assignment operator — NOT an equals sign. For example, if you write: x = 10;
do not get into the habit of saying x equals 10! That is not what’s happening. In C++, this is a valid statement: x = x + 1;
but mathematically that would be incorrect, since x cannot be equal to itself PLUS one more. Always say, «x is assigned the value of 10», or «x gets 10». It will mess you up later if you always think of the «=» sign as an equals sign — it’s not, it is an assignment operator.
Any variable, such as count
or ex
or sum
, is a name for a memory location. If we say, «x = 10;» then we are saying «Place the value of 10 into the memory location that we call x». So any variable is a container of sorts that can hold something, and obviously that is what is required on the left hand side of an assignment operator, right? An l-value is just that, a container of sorts (memory location) that can hold a value, and that must be what is placed on the left side of an assignment operator.
Imagine that you had 3 bank accounts, and they were named sum
, count
and ex
. And you had $50 in each account. Now, if you said to the bank teller: sum + count = ex
then in essence you would be saying to transfer the $50 in your ex account and place it into your sum + count account. ??? That doesn’t make sense — left operand must be an l-value! But, if you said ex = sum + count
then you would be saying «Take the $50 from my sum account and the $50 from my count account and place both in my ex account. Now that makes sense, since ex is a proper l-value, ie., one that can hold a value. Sum + count cannot hold a value, it is not a memory location, but rather is just a temporary value that is used in the middle of a calculation, and is just transient in nature. (Sum + count) is an r-value, one that goes on the right side of an assignment operator. R-values are not containers of any sort, like a memory location, but rather are just temporary values that come up in the middle of a calculation, which are soon discarded. So r-values generally go into l-values, but you had them reversed.
Just a note for clarification: ex = sum + count
— here (sum + count) replaces the value in ex, it isn’t added to the existing contents of ex
.
Edited
9 Years Ago
by ~s.o.s~ because:
Fixed formatting
MandrewP
60
Junior Poster in Training
11 Years Ago
Reply to this topic
Be a part of the DaniWeb community
We’re a friendly, industry-focused community of developers, IT pros, digital marketers,
and technology enthusiasts meeting, networking, learning, and sharing knowledge.
Хорошо, так что, игнорируя мое ленивое кодирование (это просто для того, чтобы заставить программу работать, я уберу ее после того, как она заработает). Я настроил пару операторов if, которые будут генерировать исключения, если я не получу желаемый ввод.
#include<string>
#include<iostream>
using namespace std;
int main()
{
bool flag = false;
int month, day, year;
void header();
class monthClassException
{
public:
monthClassException()
{
message = "Invalid Month";
}
monthClassException(string str)
{
message = str;
}
string what()
{
return message;
}
private:
string message;
};
class dayClassException
{
};
class yearClassException
{
};header();
do
{
try
{
cout << "Please enter your date of birth (MM-DD-YYYY): " << endl;
cin >> month;
cin.ignore(10,'-');
cin >> day;
cin.ignore(10,'-');
cin >> year;if (month > 12 || month < 1)
throw monthClassException("Invalid Month Entry");
if( ((month == 4 || month == 6 || month == 9 || month == 11) && day > 30) || day < 1)
throw dayClassException();
else if ( ((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 ) && day > 31) || day < 1)
throw dayClassException();
else if (month == 2 && year % 4 != 0 && day > 28)
throw dayClassException();
else if((month == 2 && year % 4 = 0) && day > 29)
throw dayClassException();
}
catch(monthClassException mCEO)
{
cout << mCEO.what() << endl;
system("pause");
}
catch(dayClassException)
{
cout << "Invalid Day Entered for Selected Month" << endl;
system("pause");
}
catch(yearClassException yCEO)
{
}
}while(!flag);return 0;
}
Я получаю свою ошибку в этом последнем исключении:
else if((month == 2 && year % 4 = 0) && day > 29)
throw dayClassException();
он говорит, что месяц — недопустимое l-значение (почему сейчас? В самом конце, после того, как я его уже использовал — катастрофически, я признаю.) Это может быть чем-то действительно очевидным, что я не вижу, потому что я один кто это закодировал, или это может быть потому, что я действительно сумасшедший, если заявления где-то напутали.
Есть идеи?
-3
Решение
Вот ошибка:
year % 4 = 0
ты наверное хотел написать ==
4
Другие решения
=
оператор как в
year % 4 = 0
означает назначение, а не сравнение. Отсюда твоя ошибка.
Исправить это
year % 4 == 0
0
У тебя есть year % 4 = 0
,
Я думаю, что у вас есть опечатка: вы можете хотеть year % 4 == 0
,
Кроме того, я предпочитаю использовать скобки, чтобы сделать код более понятным:
...
else if ((month == 2) && (year % 4 == 0) && (day > 29)) {
throw dayClassException();
}
0
У вас есть оператор присваивания =
в вашем состоянии вместо оператора сравнения ==
,
Это довольно ясно логическая ошибка. Тем не менее, почему это компилятор ошибка? В конце концов, C ++ позволяет присваивать внутри условия, и это то, что вы могли бы законно делать.
В твоем случае, month == 2 && year % 4 = 0
обрабатывается как ((month == 2) && (year % 4)) = 0
(увидеть Приоритет оператора C ++). Это выражение в скобках оценивается как временное. Но левая часть оператора присваивания должна ссылаться на адрес памяти, на который вы можете записать ( л-значение). Таким образом, ваш код недействителен по той же причине, что 3 = 3
является недействительным. Visual Studio вызывает эту ошибку C2106.
0
В связи с этим предлагается всегда указывать константу в левой части оператора сравнения. Это помогает предотвратить логические ошибки. В качестве примера рассмотрим код
if year == 0
и по ошибке вы написали:
if year = 0
результат был бы логической ошибкой.
Вместо этого поместите постоянную 0 на левой стороне, так что
if 0 = year
будет генерировать синтаксическую ошибку при компиляции, что не позволит вам совершить логическую ошибку (что может быть сложнее отладить)
0
Добавлено 18 июля 2021 в 21:56
При работе с массивами мы обычно используем оператор индекса ([]
) для указания на определенные элементы массива:
myArray[0] = 7; // помещаем значение 7 в первый элемент массива
Однако рассмотрим следующий класс IntList
, у которого есть переменная-член, которая является массивом:
class IntList
{
private:
int m_list[10]{};
};
int main()
{
IntList list{};
// как получить доступ к элементам из m_list?
return 0;
}
Поскольку переменная-член m_list
является закрытой, мы не можем получить к ней доступ напрямую из переменной list
. Это означает, что у нас нет возможности напрямую получать или устанавливать значения в массиве m_list
. Итак, как нам получить или поместить элементы в наш список?
Без перегрузки оператора типовым методом было бы создание функций доступа:
class IntList
{
private:
int m_list[10]{};
public:
void setItem(int index, int value) { m_list[index] = value; }
int getItem(int index) const { return m_list[index]; }
};
Хотя это работает, для пользователя это не очень удобно. Рассмотрим следующий пример:
int main()
{
IntList list{};
list.setItem(2, 3);
return 0;
}
Мы устанавливаем элемент 2 в значение 3 или элемент 3 в значение 2? Без определения setItem()
это просто непонятно.
Вы также можете просто вернуть весь список и использовать operator[]
для доступа к элементу:
class IntList
{
private:
int m_list[10]{};
public:
int* getList() { return m_list; }
};
Хотя это тоже работает, синтаксически это странно:
int main()
{
IntList list{};
list.getList()[2] = 3;
return 0;
}
Перегрузка operator[]
Однако лучшим решением в этом случае является перегрузка оператора индекса ([]
), чтобы разрешить доступ к элементам m_list
. Оператор индекса – это один из операторов, которые необходимо перегружать как функцию-член. Перегруженная функция operator[]
всегда принимает один параметр: индекс, который пользователь помещает между квадратными скобками. В случае с нашим IntList
мы ожидаем, что пользователь передаст целочисленный индекс, и в качестве результата мы вернем целочисленное значение.
class IntList
{
private:
int m_list[10]{};
public:
int& operator[] (int index);
};
int& IntList::operator[] (int index)
{
return m_list[index];
}
Теперь, когда мы используем оператор индекса ([]
) для объекта нашего класса, компилятор будет возвращать соответствующий элемент из переменной-члена m_list
! Это позволяет нам напрямую получать и устанавливать значения m_list
:
IntList list{};
list[2] = 3; // устанавливаем значение
std::cout << list[2] << 'n'; // получаем значение
Это легко как синтаксически, так и с точки зрения понимания. Когда вычисляется list[2]
, компилятор сначала проверяет, есть ли перегруженная функция operator[]
. Если она есть, он передает значение внутри квадратных скобок (в данном случае 2) в качестве аргумента функции.
Обратите внимание, что хотя вы можете указать значение по умолчанию для параметра функции, на самом деле использование operator[]
без индекса внутри считается недопустимым синтаксисом, поэтому в этом нет смысла.
Почему operator[]
возвращает ссылку
Давайте подробнее рассмотрим, как вычисляется list[2] = 3
. Поскольку оператор индекса имеет более высокий приоритет, чем оператор присваивания, list[2]
вычисляется первым. list[2]
вызывает функцию operator[]
, которую мы определили для возврата ссылки на list.m_list[2]
. Поскольку operator[]
возвращает ссылку, он возвращает фактический элемент массива list.m_list[2]
. Наше частично вычисленное выражение становится list.m_list[2] = 3
, что является простым целочисленным присваиванием.
В уроке «1.3 – Знакомство с переменными в C++» вы узнали, что любое значение в левой части оператора присваивания должно быть l-значением (которое представляет собой переменную, имеющую фактический адрес в памяти). Поскольку результат operator[]
может использоваться в левой части присваивания (например, list[2] = 3
), возвращаемое значение operator[]
должно быть l-значением. Оказывается, ссылки всегда являются l-значениями, потому что вы можете взять ссылки только на переменные, которые имеют адреса в памяти. Итак, возвращая ссылку, компилятор удовлетворен тем, что мы возвращаем l-значение.
Подумайте, что произойдет, если operator[]
вернет число int
по значению, а не по ссылке. list[2]
вызовет operator[]
, который вернет значение list.m_list[2]
. Например, если бы m_list[2]
имел значение 6, operator[]
вернул бы значение 6. list[2] = 3
частично вычислится как 6 = 3
, что не имеет смысла! Если вы попытаетесь это сделать, компилятор C++ пожалуется:
C:VCProjectsTest.cpp(386) : error C2106: '=' : left operand must be l-value
Работа с константными объектами
В приведенном выше примере IntList
функция operator[]
не является константной, и мы можем использовать ее как l-значение для изменения состояния неконстантных объектов. Однако что, если бы наш объект IntList
был константным? В этом случае мы не сможем вызвать неконстантную версию operator[]
, потому что это потенциально позволит нам изменить состояние константного объекта.
Хорошая новость в том, что мы можем определять неконстантную и константную версии operator[]
по отдельности. Неконстантная версия будет использоваться с неконстантными объектами, а константная версия – с константными объектами.
#include <iostream>
class IntList
{
private:
// задаем классу начальное состояние для этого примера
int m_list[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
public:
int& operator[] (int index);
const int& operator[] (int index) const;
};
// для неконстантных объектов: может использоваться для присваивания
int& IntList::operator[] (int index)
{
return m_list[index];
}
// для константных объектов: может использоваться только для доступа
const int& IntList::operator[] (int index) const
{
return m_list[index];
}
int main()
{
IntList list{};
// хорошо: вызывает неконстантную версию operator[]
list[2] = 3;
std::cout << list[2] << 'n';
const IntList clist{};
// ошибка компиляции: вызывает константную версию operator[],
// которая возвращает константную ссылку.
// Ей невозможно выполнить присваивание.
clist[2] = 3;
std::cout << clist[2] << 'n';
return 0;
}
Если мы закомментируем строку clist[2] = 3
, показанная выше программа будет компилироваться и выполняться, как ожидалось.
Проверка на ошибки
Еще одно преимущество перегрузки оператора индекса состоит в том, что мы можем сделать его более безопасным, чем прямой доступ к массивам. Обычно при доступе к массивам оператор индекса не проверяет, корректен ли индекс. Например, компилятор не будет жаловаться на следующий код:
int list[5]{};
list[7] = 3; // индекс 7 выходит за пределы массива!
Однако если мы знаем размер нашего массива, мы можем выполнить проверку в нашем перегруженном операторе индекса, чтобы убедиться, что индекс находится в пределах границ:
#include <cassert> // для assert()
class IntList
{
private:
int m_list[10]{};
public:
int& operator[] (int index);
};
int& IntList::operator[] (int index)
{
assert(index >= 0 && index < 10);
return m_list[index];
}
В приведенном выше примере мы использовали функцию assert()
(включенную в заголовок cassert
), чтобы убедиться, что наш индекс корректен. Если выражение внутри assert
вычисляется как false
(что означает, что пользователь передал недопустимый индекс), программа завершится с сообщением об ошибке, что намного лучше, чем альтернатива (повреждение памяти). Это, вероятно, наиболее распространенный метод проверки ошибок подобного рода.
Указатели на объекты и перегруженный operator[]
не смешиваются
Если вы попытаетесь вызвать operator[]
для указателя на объект, C++ будет считать, что вы пытаетесь указать на элемент в массиве объектов этого типа.
Рассмотрим следующий пример:
#include <cassert> // для assert()
class IntList
{
private:
int m_list[10]{};
public:
int& operator[] (int index);
};
int& IntList::operator[] (int index)
{
assert(index >= 0 && index < 10);
return m_list[index];
}
int main()
{
IntList *list{ new IntList{} };
list[2] = 3; // ошибка: это предполагает, что мы обращаемся к индексу 2 массива из IntList
delete list;
return 0;
}
Поскольку мы не можем присвоить число int
объекту IntList
, этот код не будет компилироваться. Однако если присваивание числа int
было допустимо, этот код скомпилируется и запустится с неопределенными результатами.
Правило
Убедитесь, что вы не пытаетесь вызвать перегруженный operator[]
для указателя на объект.
Правильный синтаксис – сначала разыменовать указатель (обязательно использовать круглые скобки, поскольку operator[]
имеет более высокий приоритет, чем operator*
), а затем вызвать operator[]
:
int main()
{
IntList *list{ new IntList{} };
// получаем наш объект IntList, затем вызываем перегруженный operator[]
(*list)[2] = 3;
delete list;
return 0;
}
Это выглядит коряво и подвержено ошибкам. А еще лучше не устанавливайте указатели на свои объекты, если в этом нет необходимости.
Параметр функции не обязательно должен быть числом int
Как упоминалось выше, C++ передает то, что пользователь вводит между квадратными скобками, в качестве аргумента перегруженной функции. В большинстве случаев это будет целочисленное значение. Однако в этом нет необходимости – и на самом деле вы можете определить, чтобы ваш перегруженный operator[]
принимал значение любого типа, который вам нужен. Вы можете определить свой перегруженный operator[]
так, чтобы он принимал double
, std::string
или что угодно.
В качестве нелепого примера, чтобы вы могли убедиться, что это работает:
#include <iostream>
#include <string>
class Stupid
{
private:
public:
void operator[] (const std::string& index);
};
// Нет смысла перегружать operator[], чтобы что-то напечатать,
// но это самый простой способ показать, что параметр функции
// может не быть числом int
void Stupid::operator[] (const std::string& index)
{
std::cout << index;
}
int main()
{
Stupid stupid{};
stupid["Hello, world!"];
return 0;
}
Как и следовало ожидать, этот код печатает:
Hello, world!
Перегрузка operator[]
для принятия параметра std::string
может быть полезна при написании определенных видов классов, например тех, которые используют слова в качестве индексов.
Заключение
Оператор индекса обычно перегружается, чтобы обеспечить прямой доступ к отдельным элементам из массива (или другой подобной структуры), содержащегося в классе. Поскольку строки часто реализуются как массивы символов, operator[]
часто реализуется в строковых классах, чтобы позволить пользователю получить доступ к одиночному символу строки.
Небольшой тест
Вопрос 1
Карта (map) – это класс, в котором элементы хранятся в виде пар ключ-значение. Ключ должен быть уникальным и используется для доступа к связанной паре. В этом тесте мы собираемся написать приложение, которое позволит нам присваивать оценки студентам по именам, используя простой класс карты. Имя студента будет ключом, а оценка (в виде char
) будет значением.
a) Сначала напишите структуру с именем StudentGrade
, содержащую имя студента (в виде std::string
) и оценку (в виде char
).
Ответ
#include <string> struct StudentGrade { std::string name{}; char grade{}; };
b) Добавьте класс с именем GradeMap
, который содержит std::vector
из StudentGrade
с именем m_map
.
Ответ
#include <string> #include <vector> struct StudentGrade { std::string name{}; char grade{}; }; class GradeMap { private: std::vector<StudentGrade> m_map{}; };
c) Напишите перегруженный operator[]
для этого класса. Эта функция должна принимать параметр std::string
и возвращать ссылку на char
. В теле функции сначала проверьте, существует ли уже имя студента (вы можете использовать std::find_if
из <algorithm>
). Если студент есть, верните ссылку на оценку, и всё готово. В противном случае используйте функцию std::vector::push_back()
, чтобы добавить StudentGrade
для этого нового студента. Когда вы это сделаете, std::vector
добавит себе копию вашего StudentGrade
(при необходимости изменив размер, аннулируя все ранее возвращенные ссылки). Наконец, нам нужно вернуть ссылку на оценку студента, которого мы только что добавили в std::vector
. Мы можем получить доступ к только что добавленному студенту с помощью функции std::vector::back()
.
Должна запуститься следующая программа:
#include <iostream>
// ...
int main()
{
GradeMap grades{};
grades["Joe"] = 'A';
grades["Frank"] = 'B';
std::cout << "Joe has a grade of " << grades["Joe"] << 'n';
std::cout << "Frank has a grade of " << grades["Frank"] << 'n';
return 0;
}
Ответ
#include <algorithm> #include <iostream> #include <string> #include <vector> struct StudentGrade { std::string name{}; char grade{}; }; class GradeMap { private: std::vector<StudentGrade> m_map{}; public: char& operator[](const std::string &name); }; char& GradeMap::operator[](const std::string &name) { auto found{ std::find_if(m_map.begin(), m_map.end(), [&](const auto& student){ return (student.name == name); }) }; if (found != m_map.end()) { return found->grade; } // в противном случае создать новый StudentGrade // для этого студента и добавить его в конец нашего вектора. m_map.push_back({ name }); // и вернуть этот элемент return m_map.back().grade; } int main() { GradeMap grades{}; grades["Joe"] = 'A'; grades["Frank"] = 'B'; std::cout << "Joe has a grade of " << grades["Joe"] << 'n'; std::cout << "Frank has a grade of " << grades["Frank"] << 'n'; return 0; }
Совет
Поскольку карты – это распространенный контейнер, стандартная библиотека предлагает std::map
, который в пока не рассматривался в данной серии статей. Используя std::map
, мы можем упростить наш код до следующего вида
#include <iostream>
#include <map> // std::map
#include <string>
int main()
{
// std::map можно инициализировать
std::map<std::string, char> grades{
{ "Joe", 'A' },
{ "Frank", 'B' }
};
// и присвоить
grades["Susan"] = 'C';
grades["Tom"] = 'D';
std::cout << "Joe has a grade of " << grades["Joe"] << 'n';
std::cout << "Frank has a grade of " << grades["Frank"] << 'n';
return 0;
}
Предпочитайте использовать std::map
вместо написания собственной реализации.
Вопрос 2
Дополнительный вопрос №1: Написанный нами класс GradeMap
и пример программы неэффективны по многим причинам. Опишите один способ улучшения класса GradeMap
.
Ответ
std::vector
по своей природе не отсортирован. Это означает, что каждый раз, когда мы вызываемoperator[]
, мы потенциально обходим весьstd::vector
, чтобы найти нужный элемент. С несколькими элементами это не проблема, но по мере того, как мы продолжаем добавлять имена, программа будет становиться всё медленнее. Мы могли бы оптимизировать ее, сохраняя нашуm_map
отсортированной и используя бинарный поиск, так мы минимизируем количество элементов, которые нужно будет просмотреть, чтобы найти те, которые нам интересны.
Вопрос 3
Дополнительный вопрос №2: Почему эта программа не работает, как ожидается?
#include <iostream>
int main()
{
GradeMap grades{};
char& gradeJoe{ grades["Joe"] }; // выполняет push_back
gradeJoe = 'A';
char& gradeFrank{ grades["Frank"] }; // выполняет push_back
gradeFrank = 'B';
std::cout << "Joe has a grade of " << gradeJoe << 'n';
std::cout << "Frank has a grade of " << gradeFrank << 'n';
return 0;
}
Ответ
Когда добавляется Frank,
std::vector
должен вырасти, чтобы сохранить его. Это требует динамического выделения нового блока памяти, копирования элементов массива в этот новый блок и удаления старого блока. Когда это происходит, любые ссылки на существующие элементы вstd::vector
становятся недействительными! Другими словами, после того, как мы выполнилиpush_back("Frank")
,GradeJoe
становится висячей ссылкой на удаленную память. Это приведет к неопределенному поведению.
Теги
C++ / CppLearnCppДля начинающихОбучениеОператор (программирование)Оператор индексаПерегрузка (программирование)Перегрузка операторовПрограммирование