paint-brush
Những viên ngọc ẩn của C và C++ mà bạn có thể chưa biếttừ tác giả@udalov
11,167 lượt đọc
11,167 lượt đọc

Những viên ngọc ẩn của C và C++ mà bạn có thể chưa biết

từ tác giả Ilia6m2024/08/09
Read on Terminal Reader

dài quá đọc không nổi

C++ là một ngôn ngữ tuyệt vời với quá nhiều tính năng. Tôi đã trải qua nhiều khoảnh khắc "WTF" trong suốt sự nghiệp của mình với tư cách là một nhà phát triển C++. Trong câu chuyện này, bạn sẽ tìm thấy những khía cạnh thú vị nhất của lập trình C và C++ được cô đọng dưới dạng các bài kiểm tra. Không có cách nào bạn có thể trả lời đúng nhiều hơn một vài câu hỏi.
featured image - Những viên ngọc ẩn của C và C++ mà bạn có thể chưa biết
Ilia HackerNoon profile picture
0-item
1-item

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?

  1. sẽ không biên dịch
  2. sẽ biên dịch, sẽ không liên kết
  3. 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?

  1. 1000
  2. ít hơn 1000
  3. 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. 1
  2. 2
  3. 3
  4. 4
  5. sẽ không biên dịch
  6. 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. 1 giây
  2. 1 giây
  3. 1 phút
  4. 1 giờ
  5. 1 năm
  6. 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à…


  1. Foo()
  2. Foo(Foo&&)
  3. 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ì?

  1. 0
  2. 1
  3. 0x0
  4. sẽ không biên dịch
  5. 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à…

  1. Foo(int, int)
  2. Foo(const Foo&, int)
  3. Foo(int, const Foo&)
  4. 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

  1. 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ến main với hàm main .


    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.


  2. 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 .


  3. 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.


  4. 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ỗi O(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.


  5. 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ạo const Foo&& . const Foo&& là một kiểu lạ, do đó trình biên dịch chỉ sao chép Foo


    Có hai cách để khắc phục điều này:


    1. Tạo lambda có thể thay đổi

       auto a = [f = std::move(f)]() mutable { return std::move(f); };


    2. Khai báo hàm tạo Foo(const Foo&&)


  6. 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ới int 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.


  7. 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ên i được ẩn trong phạm vi này.