C와 C++ 프로그래밍의 장엄한 세계에 합류할 준비가 되셨나요? 간단한 C++ 몇 줄을 읽고 자신의 존재에 대해 의문을 품고 싶으신가요?
만약 당신의 답이 "Yeh!", "Yep" 또는 "Why not?"라면, 당신의 지식을 테스트해 보세요. C 또는 C++와 관련된 여러 질문이 주어질 것입니다.
정답과 설명은 스토리의 끝에서 찾아보세요. 행운을 빌어요!
main;
이 프로그램을 C 컴파일러를 사용하여 컴파일하려고 하면 무슨 일이 일어날까요?
#include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }
이 프로그램은 몇 줄을 인쇄할까요?
#include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }
이 프로그램은 무엇을 인쇄할까요?
#include <regex> #include <iostream> int main() { std::regex re("(.*|.*)*O"); std::string str("0123456789"); std::cout << std::regex_match(str, re); return 0; }
이 정규 표현식이 입력 문자열과 일치하려면 얼마나 걸릴까요?
#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; }
이 프로그램이 인쇄하는 마지막 줄은…
Foo()
Foo(Foo&&)
Foo(const Foo&)
#include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }
이 프로그램은 무엇을 인쇄할까요?
0
1
0x0
#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(); }
이 프로그램이 인쇄하는 마지막 줄은…
Foo(int, int)
Foo(const Foo&, int)
Foo(int, const Foo&)
Foo(int)
이런 특이한 조각들을 야외에서 발견하지 못하기를 바랍니다.
가장 작은 프로그램
이것은 합법적인 C 코드입니다. 성공적으로 컴파일하고 링크할 것입니다. 실행하려고 하면 충돌합니다. main;
- 은 전역 변수입니다.
C 코드에서는 많은 것을 생략할 수 있습니다. 예를 들어 전역 변수의 유형을 생략할 수 있습니다. 기본적으로 컴파일러는 이 유형을 int
로 가정합니다. 또한 C에는 이름 망글링이 없으므로(C++와 달리) 링크할 때 변수 main
과 함수 main
을 구별할 방법이 없습니다.
따라서 컴파일러는 유효한 코드를 컴파일하고, 링커는 개체 파일에서 main
이라는 이름의 파일을 찾아 프로그램을 연결합니다.
포크
이것은 C 또는 C++ 기능보다는 POSIX 기능에 가깝습니다. IO 작업 구현은 성능 최적화를 위해 버퍼를 사용합니다. fork
호출하면 OS는 프로세스 메모리의 복사-쓰기 복제본을 생성하고 IO 버퍼도 복제될 가능성이 높으며 버퍼링된 문자열은 1000회 이상 인쇄될 가능성이 높습니다.
필요한 것은 인덱스뿐입니다
답은 3입니다
이 코드를 이해하기 위해 C 및 C++에서 인덱스가 작동하는 방식을 자세히 살펴보겠습니다. array[index]
는 *(array + index)
와 같고, (index + array)
와 같고, index[array
와 같습니다.
두 번째 단서는 연산자 ,
입니다. 이진 연산자로, 왼쪽 인수를 버리고 오른쪽 인수를 반환합니다.
정규 표현식
무슨 일이 일어날지 예측하는 것은 불가능 합니다! 행동은 구현에 달려 있습니다.
제 환경에서 이 프로그램은 The complexity of an attempted match against a regular expression exceeded a pre-set level.
예외를 발생시킵니다.
다른 가능한 옵션은 놀랍게도 오랜 시간이 걸리거나 예상대로 작동합니다. 정규 표현식을 구현하는 데 두 가지 가능한 접근 방식이 있기 때문입니다.
첫째 - 정규 표현식을 유한 오토마타 O(n**2)
(n - 패턴 길이)로 변환하고, 문자열 일치 O(m)
(m - 문자열 길이). 이 접근 방식은 백트래킹을 지원하지 않습니다.
두 번째 - 탐욕적 접근법 + DFS, 백트래킹을 지원하지만 특정 패턴에서는 지수적 시간 복잡도가 발생하기 쉽습니다.
이동 및 람다
답은 Foo(const Foo&)
입니다. 람다는 기본적으로 불변이며, []
로 람다에 캡처된 모든 값은 암묵적으로 const
입니다. 이를 통해 람다에 대한 멱등 동작이 잠금 해제됩니다.
f
이동하면 const Foo&&
가 생성됩니다. const Foo&&
는 이상한 유형이므로 컴파일러는 Foo
그냥 복사합니다.
이 문제를 해결하는 방법은 두 가지가 있습니다.
변경 가능한 람다 생성
auto a = [f = std::move(f)]() mutable { return std::move(f); };
생성자 Foo(const Foo&&)
선언합니다.
X와 막대
프로그램은 1
인쇄합니다 .
int bar(int(x));
— 함수를 선언하는 이상한 방법으로, int bar(int x);
와 같습니다.
형 변환과 혼동하셨다면, int bar((int(x)));
가 바로 형 변환입니다.
그런 다음 함수 주소를 bool
로 암묵적으로 캐스팅하려고 하면 이러한 캐스팅의 결과는 항상 true
입니다.
bar()
함수는 한 번도 사용되지 않았는데, 이를 통해 링크할 때 참조되지 않은 심볼 오류를 피할 수 있습니다.
생성자
마지막 줄은 Foo(const Foo&, int)
입니다.
Foo(i)
는 변수 선언이며 Foo i
와 동일합니다. 따라서 i
라는 이름의 클래스 멤버는 이 범위에서 숨겨집니다.