C ++, khai báo biến trong biểu thức 'if'


114

Những gì đang xảy ra ở đây?

if(int a = Func1())
{
    // Works.
}

if((int a = Func1()))
{
    // Fails to compile.
}

if((int a = Func1())
    && (int b = Func2()))
)
{
    // Do stuff with a and b.
    // This is what I'd really like to be able to do.
}

Phần 6.4.3 trong tiêu chuẩn 2003 giải thích cách các biến được khai báo trong điều kiện câu lệnh lựa chọn có phạm vi mở rộng đến cuối các câu lệnh con được điều kiện kiểm soát. Nhưng tôi không thấy nó nói gì về việc không thể đặt dấu ngoặc quanh khai báo, cũng như không nói gì về chỉ một khai báo cho mỗi điều kiện.

Hạn chế này gây khó chịu ngay cả trong trường hợp chỉ cần một khai báo trong điều kiện. Xem xét điều này.

bool a = false, b = true;

if(bool x = a || b)
{

}

Nếu tôi muốn nhập phạm vi 'if "-body với x được đặt thành false thì khai báo cần có dấu ngoặc đơn (vì toán tử gán có mức độ ưu tiên thấp hơn OR logic), nhưng vì không thể sử dụng dấu ngoặc nên yêu cầu khai báo x bên ngoài body, làm rò rỉ khai báo đó đến một phạm vi lớn hơn mong muốn. Rõ ràng ví dụ này là nhỏ nhặt nhưng trường hợp thực tế hơn sẽ là một trường hợp trong đó a và b là các hàm trả về giá trị cần được kiểm tra

Vì vậy, có phải những gì tôi muốn làm không phù hợp với tiêu chuẩn, hay trình biên dịch của tôi chỉ làm hỏng các quả bóng của tôi (VS2008)?


6
"If I want to enter the loop with" <- ví dụ của bạn có if.ifkhông phải là một vòng lặp, nó là một điều kiện.
crashmstr

2
@crashmstr: true, nhưng các điều kiện cho while cũng giống như cho if.
Mike Seymour

2
Không thể thực hiện điều này với toán tử dấu phẩy? Ý tôi là if (int a = foo(), int b = bar(), a && b):? Nếu toán tử dấu phẩy không được nạp chồng, tiêu chuẩn nói rằng các biểu thức được đánh giá từ trái sang phải và giá trị kết quả là biểu thức cuối cùng. Nó hoạt động với forkhởi tạo vòng lặp, tại sao không ở đây?
Archie,

@Archie: Tôi chỉ thử cái này, tôi không thể làm cho nó hoạt động được. Có lẽ bạn có thể cung cấp một ví dụ làm việc?
James Johnston

@JamesJohnston: Tôi cũng đã thử, và nó có vẻ không hiệu quả. Ý tưởng đó chỉ nảy ra trong đầu tôi, tôi đã được gợi ý bởi cách thức ifhoạt động, và có vẻ như đó là một giả định sai.
Archie

Câu trả lời:


63

Kể từ C ++ 17, những gì bạn đang cố gắng làm cuối cùng cũng có thể thực hiện được :

if (int a = Func1(), b = Func2(); a && b)
{
    // Do stuff with a and b.
}

Lưu ý việc sử dụng ; thay vì ,để tách biệt giữa khai báo và thực trạng.


23
Đẹp! Tôi luôn nghi ngờ mình đã đi trước thời đại.
Neutrino

106

Tôi nghĩ rằng bạn đã gợi ý về vấn đề này. Trình biên dịch phải làm gì với mã này?

if (!((1 == 0) && (bool a = false))) {
    // what is "a" initialized to?

Toán tử "&&" là một logic ngắn mạch AND. Điều đó có nghĩa là nếu phần đầu tiên (1==0)là sai, thì (bool a = false)không nên đánh giá phần thứ hai vì người ta đã biết rằng câu trả lời cuối cùng sẽ là sai. Nếu (bool a = false)không được đánh giá, thì phải làm gì với mã sau này sử dụng a? Liệu chúng ta có thể không khởi tạo biến và để nó không xác định? Chúng tôi sẽ khởi tạo nó thành mặc định? Điều gì sẽ xảy ra nếu kiểu dữ liệu là một lớp và việc làm này có tác dụng phụ không mong muốn? Điều gì sẽ xảy ra nếu thay vì boolbạn sử dụng một lớp và nó không có hàm tạo mặc định để người dùng phải cung cấp các tham số - sau đó chúng ta phải làm gì?

Đây là một ví dụ khác:

class Test {
public:
    // note that no default constructor is provided and user MUST
    // provide some value for parameter "p"
    Test(int p);
}

if (!((1 == 0) && (Test a = Test(5)))) {
    // now what do we do?!  what is "a" set to?

Có vẻ như giới hạn mà bạn đã tìm thấy có vẻ hoàn toàn hợp lý - nó ngăn những loại mơ hồ này xảy ra.


1
Điểm tốt. Bạn có thể muốn đề cập đến hiện tượng đoản mạch một cách rõ ràng, trong trường hợp OP hoặc những người khác không quen thuộc với nó.
Chris Cooper

7
Tôi đã không nghĩ về điều đó. Mặc dù trong ví dụ bạn đã cung cấp, dấu gạch ngắn ngăn không cho nhập phạm vi câu lệnh điều kiện, trong trường hợp đó, phần biểu thức khai báo biến thể không được xử lý không phải là một vấn đề, vì phạm vi của nó được giới hạn trong phạm vi của câu lệnh điều kiện. Trong trường hợp đó sẽ không tốt hơn nếu trình biên dịch chỉ đưa ra lỗi trong những trường hợp có khả năng phạm vi câu lệnh điều kiện được nhập khi một phần của biểu thức khai báo một biến không được xử lý? Đó không phải là trường hợp trong các ví dụ tôi đã đưa ra.
Neutrino

@Neutrino Ngay từ cái nhìn đầu tiên, bạn nghĩ có vẻ hơi giống bài toán SAT, bài toán này không dễ giải lắm, ít nhất là trong trường hợp chung.
Christian Rau

5
Những gì bạn giải thích về tất cả các vấn đề với việc có nhiều khai báo biến trong điều kiện if và thực tế là bạn chỉ có thể sử dụng chúng một cách hạn chế khiến tôi tự hỏi tại sao loại khai báo này lại được giới thiệu ngay từ đầu. Tôi chưa bao giờ cảm thấy cần một cú pháp như vậy trước khi tôi thấy nó trong một số ví dụ mã. Tôi thấy cú pháp này khá vụng về và tôi nghĩ rằng việc khai báo biến trước khối if dễ đọc hơn nhiều. Nếu bạn thực sự cần hạn chế phạm vi của biến đó, bạn có thể đặt thêm một khối xung quanh khối if. Tôi chưa bao giờ sử dụng cú pháp này.
Giorgio

2
Cá nhân tôi nghĩ rằng sẽ khá thú vị khi có thể hạn chế phạm vi của các biến mà bạn đang sử dụng vào phạm vi chính xác của khối câu lệnh cần sử dụng chúng, mà không cần phải sử dụng các biện pháp xấu xí như thêm dấu ngoặc nhọn phạm vi lồng nhau.
Neutrino

96

Điều kiện trong một ifhoặc whilecâu lệnh có thể là một biểu thức hoặc một khai báo biến đơn (có khởi tạo).

Ví dụ thứ hai và thứ ba của bạn không phải là biểu thức hợp lệ, cũng không phải là khai báo hợp lệ, vì khai báo không thể tạo thành một phần của biểu thức. Mặc dù sẽ rất hữu ích nếu có thể viết mã như ví dụ thứ ba của bạn, nhưng nó sẽ yêu cầu một sự thay đổi đáng kể đối với cú pháp ngôn ngữ.

Tôi không thấy nó nói gì về việc không thể đặt dấu ngoặc quanh khai báo, cũng như không nói gì về việc chỉ có một khai báo cho mỗi điều kiện.

Đặc tả cú pháp trong 6.4 / 1 cho điều kiện sau:

condition:
    expression
    type-specifier-seq declarator = assignment-expression

chỉ định một khai báo duy nhất, không có dấu ngoặc đơn hoặc các phần tô điểm khác.


3
Có lý do hoặc nền tảng nào cho việc này không?
Tomáš Zato - Phục hồi Monica

23

Nếu bạn muốn bao gồm các biến trong một phạm vi hẹp hơn, bạn luôn có thể sử dụng { }

//just use { and }
{
    bool a = false, b = true;

    if(bool x = a || b)
    {
        //...
    }
}//a and b are out of scope

5
+1. Ngoài ra, tôi sẽ di chuyển khai báo của x sang khối xung quanh: tại sao nó phải có trạng thái đặc biệt wrt a và b?
Giorgio

1
Rõ ràng, nhưng không thuyết phục: Điều tương tự cũng có thể được nói đối với các biến vòng lặp thông thường. (Cấp, nhu cầu về phạm vi biến hạn chế là phổ biến hơn nhiều trong vòng.)
Peter - Khôi phục Monica

18

Phần cuối cùng đã hoạt động, bạn chỉ cần viết nó hơi khác một chút:

if (int a = Func1())
{
   if (int b = Func2())
   {
        // do stuff with a and b
   }
}

2

Đây là một cách giải quyết xấu khi sử dụng vòng lặp (nếu cả hai biến đều là số nguyên):

#include <iostream>

int func1()
{
    return 4;
}

int func2()
{
    return 23;
}

int main()
{
    for (int a = func1(), b = func2(), i = 0;
        i == 0 && a && b; i++)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }

    return 0;
}

Nhưng điều này sẽ gây nhầm lẫn cho các lập trình viên khác và nó là mã khá tệ, vì vậy không được khuyến khích.

Một {}khối bao quanh đơn giản (như đã được khuyến nghị) dễ đọc hơn nhiều:

{
    int a = func1();
    int b = func2();

    if (a && b)
    {
        std::cout << "a = " << a << std::endl;
        std::cout << "b = " << b << std::endl;
    }
}

1

Một điều cần lưu ý, đó là các biểu thức bên trong khối if lớn hơn

if (!((1 == 0) && (bool a = false)))

không nhất thiết phải đảm bảo được đánh giá theo kiểu từ trái sang phải. Một lỗi khá tinh vi mà tôi đã gặp phải trong ngày hôm đó liên quan đến việc trình biên dịch thực sự đang thử nghiệm từ phải sang trái thay vì từ trái sang phải.


5
Tuy nhiên, ngày nay, C99 yêu cầu điều đó && và || được đánh giá từ trái sang phải.
b0fh

2
Tôi nghĩ rằng đánh giá từ phải sang trái của các đối số là không thể thực hiện được do logic ngắn mạch. Nó luôn được sử dụng cho những thứ như kiểm tra con trỏ và con trỏ trong một biểu thức duy nhất, chẳng hạn như if(p && p->str && *p->str) .... Từ phải sang trái sẽ rất nguy hiểm và không tinh tế. Với các toán tử khác - chỉ định ngay cả! - và các đối số gọi hàm bạn đúng, nhưng không đúng với các toán tử ngắn mạch.
Peter - Khôi phục Monica

1

Với một phép thuật mẫu nhỏ, bạn có thể giải quyết vấn đề không thể khai báo nhiều biến:

#include <stdio.h>

template <class LHS, class RHS>
struct And_t {
  LHS lhs;
  RHS rhs;

  operator bool () {
    bool b_lhs(lhs);
    bool b_rhs(rhs);
    return b_lhs && b_rhs;
  }
};
template <class LHS, class RHS> 
And_t<LHS, RHS> And(const LHS& lhs, const RHS& rhs) { return {lhs, rhs}; }

template <class LHS, class RHS>
struct Or_t {
LHS lhs;
RHS rhs;

  operator bool () {
    bool b_lhs(lhs);
    bool b_rhs(rhs);
    return b_lhs || b_rhs;
  }
};
template <class LHS, class RHS> 
Or_t<LHS, RHS> Or(const LHS& lhs, const RHS& rhs) { return {lhs, rhs}; }

int main() {
  if (auto i = And(1, Or(0, 3))) {
    printf("%d %d %d\n", i.lhs, i.rhs.lhs, i.rhs.rhs);
  }
  return 0;
}

(Lưu ý, điều này làm mất đánh giá ngắn mạch.)


Tôi giả định nó mất (hay nới lỏng?)
Peter - Khôi phục Monica

5
Tôi nghĩ rằng mục đích của OP là mã ngắn gọn, rõ ràng và dễ bảo trì. Bạn đề xuất "giải pháp" làm ngược lại.
Dženan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.