Bạn đã sẵn sàng tham gia vào thế giới hùng vĩ của lập trình C và C++ chưa? Bạn có muốn đặt câu hỏi về sự tồn tại của mình sau vài dòng lệnh C++ đơn giản không?
Nếu câu trả lời của bạn là "Yeh!", "Yep" hoặc "Why not?" - chào mừng bạn đến để kiểm tra kiến thức của bạn. Bạn sẽ được đưa ra nhiều câu hỏi liên quan đến C hoặc C++.
Hãy tìm câu trả lời và lời giải thích đúng ở cuối câu chuyện. Chúc may mắn!
1. Chương trình nhỏ nhất
main;
Điều gì sẽ xảy ra nếu bạn thử biên dịch chương trình này bằng trình biên dịch C?
- sẽ không biên dịch
- sẽ biên dịch, sẽ không liên kết
- sẽ biên dịch và sẽ liên kết
2. Cái nĩa
#include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }
Chương trình này có thể in ra bao nhiêu dòng?
- 1000
- ít hơn 1000
- hơn 1000
3. Tất cả những gì bạn cần là chỉ số
#include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }
Chương trình này sẽ in ra những gì?
- 1
- 2
- 3
- 4
- sẽ không biên dịch
- không xác định
4. Biểu thức chính quy
#include <regex> #include <iostream> int main() { std::regex re("(.*|.*)*O"); std::string str("0123456789"); std::cout << std::regex_match(str, re); return 0; }
Phải mất bao lâu để biểu thức chính quy này khớp với chuỗi đầu vào này?
- 1 giây
- 1 giây
- 1 phút
- 1 giờ
- 1 năm
- mãi mãi
5. Di chuyển và lambda
#include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(Foo&&) { std::cout << "Foo(Foo&&)\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } }; int main() { Foo f; auto a = [f = std::move(f)]() { return std::move(f); }; Foo f2(a()); return 0; }
Dòng cuối cùng được chương trình này in ra là…
-
Foo()
-
Foo(Foo&&)
-
Foo(const Foo&)
6. X và thanh
#include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }
Chương trình này sẽ in ra những gì?
-
0
-
1
-
0x0
- sẽ không biên dịch
- sẽ không liên kết
7. Các nhà xây dựng
#include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } Foo(int) { std::cout << "Foo(int)\n"; } Foo(int, int) { std::cout << "Foo(int, int)\n"; } Foo(const Foo&, int) { std::cout << "Foo(const Foo&, int)\n"; } Foo(int, const Foo&) { std::cout << "Foo(int, const Foo&)\n"; } }; void f(Foo) {} struct Bar { int i, j; Bar() { f(Foo(i, j)); f(Foo(i)); Foo(i, j); Foo(i); Foo(i, j); } }; int main() { Bar(); }
Dòng cuối cùng được chương trình này in ra là…
-
Foo(int, int)
-
Foo(const Foo&, int)
-
Foo(int, const Foo&)
-
Foo(int)
Thay vì kết luận
Tôi hy vọng bạn sẽ không bao giờ tìm thấy một trong những mẩu thông tin kỳ lạ này ngoài tự nhiên.
Câu trả lời
Chương trình nhỏ nhất
Đây là mã C hợp lệ. Nó sẽ biên dịch và liên kết thành công. Nó sẽ bị sập nếu bạn cố chạy nó.
main;
- là biến toàn cục.
Trong mã C, bạn có thể bỏ qua rất nhiều thứ. Ví dụ, bạn có thể bỏ qua kiểu của một biến toàn cục. Theo mặc định, trình biên dịch sẽ cho rằng kiểu này là
int
. Ngoài ra, không có sự thay đổi tên trong C (không giống như trong C++), vì vậy khi liên kết, không có cách nào để phân biệt biếnmain
với hàmmain
.
Do đó, trình biên dịch sẽ biên dịch mã hợp lệ và trình liên kết sẽ tìm mục có tên là
main
trong tệp đối tượng để liên kết chương trình.
Cái nĩa
Đây là tính năng POSIX hơn là tính năng C hoặc C++. Việc triển khai các hoạt động IO sử dụng bộ đệm để tối ưu hóa hiệu suất. Khi bạn gọi
fork
, hệ điều hành sẽ tạo bản sao chép khi ghi của bộ nhớ quy trình, bộ đệm IO có khả năng cũng sẽ sao chép và các chuỗi được đệm có khả năng sẽ được in hơn 1000 lần .
Tất cả những gì bạn cần là chỉ số
Câu trả lời là 3
Để hiểu đoạn mã này, chúng ta hãy xem xét kỹ hơn cách các chỉ mục trong C và C++ hoạt động:
array[index]
giống như*(array + index)
, giống như(index + array)
và giống nhưindex[array
.Manh mối thứ hai là toán tử
,
. Toán tử nhị phân của nó là loại bỏ đối số bên trái và trả về đối số bên phải.
Biểu thức chính quy
Không thể dự đoán được điều gì sẽ xảy ra! Hành vi phụ thuộc vào việc thực hiện.
Trong môi trường của tôi, chương trình này đưa ra ngoại lệ
The complexity of an attempted match against a regular expression exceeded a pre-set level.
Các lựa chọn khả thi khác có thời gian thực hiện lâu đáng ngạc nhiên hoặc hoạt động như mong đợi. Đó là vì có hai cách tiếp cận khả thi để triển khai biểu thức chính quy.
Đầu tiên - chuyển đổi biểu thức chính quy thành automata hữu hạn
O(n**2)
(n - độ dài của mẫu), khớp chuỗiO(m)
(m - độ dài của chuỗi). Phương pháp này không hỗ trợ quay lui.
Thứ hai - phương pháp tham lam + DFS, hỗ trợ quay lui nhưng dễ bị phức tạp theo cấp số nhân đối với một số mẫu nhất định.
Di chuyển và lambda
Câu trả lời là
Foo(const Foo&)
. Lambdas mặc định là bất biến, tất cả các giá trị được ghi vào lambda với[]
đều ngầm định làconst
. Điều này mở khóa hành vi idempotent cho lambdas.
Khi bạn di chuyển
f
bạn tạoconst Foo&&
.const Foo&&
là một kiểu lạ, do đó trình biên dịch chỉ sao chépFoo
Có hai cách để khắc phục điều này:
Tạo lambda có thể thay đổi
auto a = [f = std::move(f)]() mutable { return std::move(f); };
Khai báo hàm tạo
Foo(const Foo&&)
X và thanh
Chương trình sẽ in ra
1
.
int bar(int(x));
— là cách kỳ lạ để khai báo hàm, nó bằng vớiint bar(int x);
.Nếu bạn nhầm lẫn với ép kiểu,
int bar((int(x)));
- đây là ép kiểu.
Sau đó chúng ta thử chuyển đổi ngầm địa chỉ hàm thành
bool
, kết quả của việc chuyển đổi như vậy luôn luôn làtrue
.
Hàm
bar()
chưa bao giờ được sử dụng, nó cho phép chúng ta tránh lỗi ký hiệu không được tham chiếu khi liên kết.
Các nhà xây dựng
Dòng cuối cùng là
Foo(const Foo&, int)
.
Foo(i)
là khai báo biến, giống nhưFoo i
. Do đó, thành viên lớp dưới têni
được ẩn trong phạm vi này.