OOP 언어로 편향된 후 C 프로그래머로 생각하는 방법? [닫은]


38

이전에는 객체 지향 프로그래밍 언어 (C ++, Ruby, Python, PHP) 만 사용했으며 이제는 C를 배우고 있습니다. 언어 개념이없는 언어로 작업을 수행하는 올바른 방법을 찾기가 어렵습니다. '목적'. C에서 OOP 패러다임을 사용할 수 있다는 것을 알고 있지만 C- 아이디 오마 틱 방식을 배우고 싶습니다.

프로그래밍 문제를 해결할 때 가장 먼저해야 할 일은 문제를 해결할 객체를 상상하는 것입니다. 비 OOP 명령형 프로그래밍 패러다임을 사용할 때이를 대체하는 단계는 무엇입니까?


15
나는 내 사고 방식과 가장 일치하는 언어를 아직 찾지 못했기 때문에 사용중인 모든 언어에 대한 내 생각을“컴파일”해야합니다. 내가 찾은 유용한 개념 중 하나는 레이블, 서브 루틴, 함수, 객체, 모듈 또는 프레임 워크인지 여부에 관계없이 "코드 단위"라는 개념입니다. 이들 모두는 캡슐화되고 잘 정의 된 인터페이스를 제공해야합니다. 하향식 객체 수준 접근 방식을 사용하는 경우 C에서 문제가 해결 된 것처럼 동작하는 일련의 함수를 작성하여 시작할 수 있습니다. 잘 설계된 C API는 종종 OOP처럼 보이지만 qux = foo.bar(baz)이됩니다 qux = Foo_bar(foo, baz).
— amon

amon 을 반향하려면 그래프와 같은 데이터 구조, 포인터, 알고리즘, 코드 (함수)의 실행 (제어 흐름), 함수 포인터에 중점을 둡니다.
— rwong

1
LibTiff (github의 소스 코드) 는 큰 C 프로그램을 구성하는 방법의 예입니다.
— rwong

1
C # 프로그래머로서 나는 객체를 그리워하는 것보다 훨씬 더 델리게이트 (하나의 바운드 매개 변수가있는 함수 포인터)를 그리워합니다.
— 코드 InChaos

나는 전처리기를 제외하고는 대부분의 C를 쉽고 간단하게 발견했습니다. 만약 C를 다시 배워야한다면, 그것은 제가 많은 노력에 집중할 수있는 한 분야 일 것입니다.
— biziclop

답변:


53
  • AC 프로그램은 기능의 모음입니다.
  • 함수는 문장의 모음입니다.
  • 로 데이터를 캡슐화 할 수 있습니다 struct.

그게 다야.

수업은 어떻게 했습니까? .C 파일을 작성하는 방식과 거의 같습니다. 물론, 메소드 다형성 및 상속과 같은 것은 얻지 못하지만 함수 이름과 구성 이 다른 것을 시뮬레이션 할 수 있습니다 .

길을 개척하려면 Functional Programming을 공부하십시오 . 수업 없이 할 수있는 일은 정말 놀랍습니다 . 수업의 오버 헤드없이 실제로 더 잘 작동하는 것이 있습니다.

ANSI C의 추가
객체 지향


9
당신은 또한 typedef그것을 할 수 struct있고 클래스 같은 것을 만들 수 있습니다 . 및 typedef-ed 유형이 다른에 포함 할 수있는 struct자신이 될 수 있다는 것을의 typedef-ed. C로 얻을 수없는 것은 연산자 오버로딩과 클래스와 C ++로 얻는 멤버의 피상적으로 간단한 상속입니다. C ++에서 얻을 수있는 이상하고 부 자연스러운 구문을 많이 얻지 못합니다. 나는 OOP의 개념을 정말 좋아하지만 C ++은 OOP의 추악한 구현이라고 생각합니다. C와 같은 내가이 있기 때문 이다 최고의 기능에 남아있는 언어와 구문 밖으로 작은 언어와 잎.
— robert bristow-johnson 22시 52 분

22
모국어가 C 인 사람으로서 저는 그렇게 말하려고합니다 . a lot of things actually work better without the overhead of classes
— haneefmubarak 2016 년

1
확장하기 위해 운영 체제, 프로토콜 서버, 부트 로더, 브라우저 등 OOP없이 많은 것들이 개발되었습니다. 컴퓨터는 물체의 관점에서 생각하지 않으며 필요하지도 않습니다. 실제로, 그것들을 강제하는 것은 종종 꽤 느립니다 .
— edmz 2016 년

대위법 : a lot of things actually work better with addition of class-based OOP. 출처 : TypeScript, Dart, CoffeeScript 및 업계가 기능 / 시제품 OOP 언어에서 벗어나려고하는 다른 모든 방법.
— Den

확장하려면 많은 일들이 개발되었다 와 다른 모든 것들 : OOP. 인간은 자연스럽게 사물이라는 관점에서 생각하며 다른 인간이 읽고 이해하도록 프로그램이 작성됩니다.
— Den

18

SICP를 읽고 체계와 추상 데이터 유형에 대한 실용적인 아이디어를 배우 십시오 . 그런 다음 C로 코딩하는 것이 쉽습니다 (SICP, 약간의 C 및 약간의 PHP, Ruby 등으로 인해 생각이 충분히 넓어지고 객체 지향 프로그래밍이 최상의 스타일이 아닐 수도 있음을 이해할 것입니다 모든 경우에 해당하지만 특정 종류의 프로그램에만 해당). 가장 어려운 부분 인 C 동적 메모리 할당에 주의하십시오 . C99 이나 C11 프로그래밍 언어 표준의 C 표준 라이브러리 (이! TCP 또는 디렉토리에 대해 알고하지 않습니다), 실제로 아주 가난한 자주 일부 외부 라이브러리가 필요합니다 또는 인터페이스 (예 :POSIX , libcurl에서 HTTP 클라이언트 라이브러리, libonion HTTP 서버 라이브러리, GMPlib bignums를 들어, 같은 몇 가지 라이브러리 libunistring ) ... 등, UTF-8.

당신의 "객체들"은 종종 C와 관련이 struct있으며, 당신은 그들에 작용하는 함수 세트를 정의합니다. 짧거나 매우 간단한 함수의 경우 일부 헤더 파일 에서와 같이 -d struct로 관련되는 관련을 사용하여 함수를 정의하는 것이 좋습니다.static inlinefoo.h#include

객체 지향 프로그래밍 만이 프로그래밍 패러다임 이 아님에 주목하십시오 . 어떤 경우에는 다른 패러다임이 가치가 있습니다 ( 기능 프로그래밍 -La Ocaml 또는 Haskell 또는 Scheme 또는 Commmon Lisp, 논리 프로그래밍 -La Prolog 등 ... 선언적 인공 지능에 대한 J.Pitrat의 블로그 도 읽어보십시오 ). Scott의 책 : Programming Language Pragmatics 참조

실제로 C 또는 Ocaml의 프로그래머는 일반적으로 객체 지향 프로그래밍 스타일로 코딩하기를 원하지 않습니다. 유용하지 않을 때 객체를 생각하도록 강요 할 이유가 없습니다.

struct포인터와 그 위에서 작동하는 함수 및 함수를 정의 합니다. 당신은 몇 가지 필요한 수 태그 노조 (종종 struct태그 멤버, 종종 일부와 enum, 일부 union내부), 당신은이 유용 찾을 수있는 유연한 배열 구성원 여러분 중 일부의 끝에 struct-s를.

일부 기존의 소스 코드 내부를 살펴 무료 소프트웨어 에서 C (참조 GitHub의 및 소스 포지 일부를 찾을 수 있습니다). 아마도 Linux 배포판을 설치하고 사용하는 것이 유용 할 것입니다. 거의 무료 소프트웨어로 만들어졌으며 훌륭한 무료 소프트웨어 C 컴파일러 ( GCC , Clang / LLVM ) 및 개발 도구가 있습니다. Linux 용 으로 개발하려면 고급 Linux 프로그래밍을 참조하십시오 .

예를 들어 모든 경고와 디버그 정보로 컴파일하는 것을 잊지 마세요 gcc -Wall -Wextra -g-notably 개발 및 디버깅 phases- 동안과 몇 가지 도구, 예를 들어, 사용하는 방법을 배우게 Valgrind의를 사냥에 메모리 누수 는 gdb주의하십시오 등 디버거 되어 잘 이해 정의되지 않은 행동 과 강하게을 피하기는 (프로그램이 일부 UB가 때로는 "작업"으로 보일 수 있다는 것을 기억).

객체 지향 구조 (특히 상속 ) 가 진정 필요할 때 관련 구조 및 함수에 대한 포인터를 사용할 수 있습니다. 당신은 자신의 vtable 기계를 가질 수 있고, 각각의 "객체"를 struct포함하는 함수 포인터에 대한 포인터로 시작할 수 있습니다. 포인터 유형을 다른 포인터 유형으로 캐스트하는 기능 (및 상속을 에뮬레이트하기 위해 struct super_st시작하는 필드 유형과 동일한 필드 유형을 포함하여 캐스트 할 수 있는 기능 struct sub_st)을 이용하십시오. 공지 사항 C는 몇 가지에 따라 특정 -in 매우 복잡한 객체 시스템을 구현하기에 충분한 것으로 규칙을 - 같은 G 객체 (GTK / 그놈에서) 보여줍니다.

당신이 진정으로 필요로 할 때 클로저를 , 당신은거야 종종 그들을 모방 콜백 과, 컨벤션 (이것은 그 호출 함수 포인터에 의해 소비) 콜백을 사용하는 모든 함수는 함수 포인터와 일부 클라이언트 데이터를 모두 통과된다. 또한 (종래에) 자신의 클로저와 같은 struct-s (일부 함수 포인터와 닫힌 값 포함 )를 가질 수 있습니다 .

C는 매우 낮은 수준의 언어이므로 특히 메모리 관리 및 일부 명명 규칙 과 관련하여 고유 한 규칙 (다른 C 프로그램의 실습에서 영감을 받음) 을 정의하고 문서화하는 것이 중요합니다 . 명령어 세트 아키텍처 에 대한 아이디어를 갖는 것이 유용합니다 . 잊지 마세요 C의 컴파일러를 많이 할 수 최적화를 코드에 (당신에게 그것을 요구하는 경우), 그래서 (당신의 컴파일러, 손으로 휴가를 마이크로 최적화하는 것에 대해 너무 많이 걱정하지 않는다 발표의 최적화 된 컴파일 소프트웨어). 벤치마킹 및 원시 성능에 관심이있는 경우 프로그램을 디버깅 한 후에 최적화를 활성화해야합니다.gcc -Wall -O2

때때로 메타 프로그래밍 이 유용하다는 것을 잊지 마십시오 . 종종 C로 작성된 대형 소프트웨어에는 다른 곳에서 사용되는 일부 C 코드를 생성하는 스크립트 또는 임시 프로그램이 포함되어 있습니다 (그리고 X-macros 와 같은 더러운 C 전 처리기 트릭을 재생할 수도 있습니다 ). 유용한 C 프로그램 생성기 (예 : 파서를 생성하는 yacc 또는 gnu bison , 완벽한 해시 함수를 생성하는 gperf 등)가 있습니다. 일부 시스템 (특히 Linux 및 POSIX)에서는 파일 에서 런타임에 일부 C 코드를 생성하고 런타임에 일부 명령 (예 :) 을 실행하여 공유 객체로 컴파일하고 dlopen을 사용하여 공유 객체를 동적으로로드 할 수 있습니다generated-001.cgcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so& dlsym을 사용하여 이름에서 함수 포인터를 얻습니다 . 나는 이러한 트릭하고 있어요 MELT (그것은 사용자 정의 할 수 있기 때문에, 당신에게 도움이 될 수있는 도메인 리스프와 같은 특정 언어 의 GCC 컴파일러).

알고 있어야 가비지 컬렉션 (개념과 기술 참조 횟수가 종종 C에서 메모리를 관리 할 수있는 기술이며, 그것을 잘 처리하지 않는 쓰레기 수집의 가난한 형태 이럴입니다 순환 참조 , 당신은 할 수 약한 포인터를 그것에 대해 도움말을, 그러나 까다로울 수 있습니다). 경우에 따라 Boehm의 보수 가비지 수집기 사용을 고려할 수도 있습니다 .


7
솔직히이 질문에서 SICP를 읽는 것은 의심 할 여지없이 좋은 조언이지만 OP에 대해서는 다음 질문 인 "SICP로 편향된 후 C 프로그래머로 생각하는 방법"으로 이어질 것입니다.
— Doc Brown

1
SICP & PHP (또는 Ruby 또는 Python)의 Scheme이 너무 다르기 때문에 OP가 훨씬 더 넓은 사고를 얻을 수 있습니다. SICP는 실제로 추상 데이터 유형이 무엇인지 잘 설명하고 있으며 특히 C로 코딩 할 때 이해하는 데 매우 유용합니다.
— Basile Starynkevitch 2018 년

1
SICP는 이상한 제안입니다. 구성표는 C와 매우 다릅니다.
— Brian Gordon

그러나 SICP는 많은 좋은 습관을 가르치고 있으며 Scheme이 C로 코딩 할 때 도움이
— 된다는

5

프로그램이 구축되는 방식은 기본적으로 문제를 해결하기 위해 수행해야하는 작업 (기능)을 정의하는 것입니다 (즉, 절차 언어라고 함). 각 동작은 기능에 해당합니다. 그런 다음 각 함수가받을 정보 종류와 반환해야 할 정보를 정의해야합니다.

프로그램은 일반적으로 파일 (모듈)로 구분되며 각 파일에는 일반적으로 관련된 기능 그룹이 있습니다. 각 파일의 시작 부분에서 해당 파일의 모든 함수가 사용할 변수를 선언하십시오 (함수 외부). "정적"한정자를 사용하면 해당 변수는 해당 파일 내에서만 볼 수 있지만 다른 파일에서는 볼 수 없습니다. 함수 외부에서 정의 된 변수에 "정적"한정자를 사용하지 않으면 다른 파일에서도 액세스 할 수 있으며 이러한 다른 파일은 변수를 "extern"으로 선언해야하지만 정의하지 않아야합니다. 다른 파일에서.

간단히 말해 먼저 절차 (기능)에 대해 생각한 다음 모든 기능이 필요한 정보에 액세스 할 수 있는지 확인하십시오.


3

C API는 자주 - 어쩌면, 보통 - 당신이 그들에 적절한 방법을 보면 본질적으로 객체 지향 인터페이스를 가지고 않습니다.

C ++에서 :

class foo {
    public:
        foo (int x);
        void bar (int param);
    private:
        int x;
};

// Example use:
foo f(42);
f.bar(23);

C에서 :

typedef struct {
    int x;
} foo;

void bar (foo*, int param);

// Example use:
foo f = { .x = 42 };
bar(&f, 23);

아시다시피, C ++ 및 다양한 다른 공식 OO 언어에서 객체 메소드는 bar()위 의 C 버전과 마찬가지로 객체에 대한 포인터 인 첫 번째 인수를 사용합니다 . 이것이 C ++에서 표면에 나오는 예를 보려면 std::bind객체 메소드를 함수 서명에 맞추는 방법을 고려하십시오 .

new function<void(int)> (
    bind(&foo::bar, this, placeholders::_1)
//                  ^^^^ object pointer as first arg
);

다른 사람들이 지적했듯이, 실제 차이점은 공식 OO 언어는 다형성, 액세스 제어 및 기타 다양한 멋진 기능을 구현할 수 있다는 것입니다. 그러나 객체 지향 프로그래밍의 본질, 이산적이고 복잡한 데이터 구조의 생성 및 조작은 이미 C의 기본 관행입니다.


2

사람들이 C를 배우도록 장려하는 가장 큰 이유 중 하나는 C 언어가 가장 높은 수준의 프로그래밍 언어 중 하나이기 때문입니다. OOP 언어를 사용하면 데이터 모델과 템플릿 코드 및 메시지 전달에 대해 쉽게 생각할 수 있지만, 하루가 끝나면 마이크로 프로세서가 단계별로 코드를 실행하고 코드 블록 안팎으로 점프 (C의 함수) 및 이동 프로그램의 다른 부분이 데이터를 공유 할 수 있도록 변수 (C의 포인터)에 대한 참조. C를 영어로 된 어셈블리 언어로 생각하면 컴퓨터의 마이크로 프로세서에 단계별 지침을 제공 할 수 있습니다. 또한 대부분의 운영 체제 인터페이스는 OOP 패러다임이 아닌 C 함수 호출처럼 작동합니다.


2
IMHO C는 저수준 언어이지만 C 컴파일러가 많은 저수준 최적화를 수행 할 수 있기 때문에 어셈블러 또는 머신 코드보다 훨씬 높습니다.
— Basile Starynkevitch 2016 년

C 컴파일러는 또한 "최적화"라는 이름으로 기계에 코드의 자연스러운 동작이 있더라도 입력이 주어질 때 시간과 인과 관계를 무시할 수있는 추상 기계 모델로 이동하고 있습니다. 달리 실행하면 요구 사항을 충족합니다. 예를 들어, 기능 은 16 비트이거나 33 비트 이상인 uint16_t blah(uint16_t x) {return x*x;}시스템에서 동일하게 작동합니다 unsigned int. unsigned int그러나 17 비트에서 32 비트 인 기계의 일부 컴파일러는 해당 메소드에 대한 호출을 고려할 수 있습니다.
— supercat

... 컴파일러가 46340을 초과하는 값을 메소드에 부여 할 수있는 이벤트 체인을 유추 할 수있는 권한을 부여 할 수 있습니다. 모든 플랫폼에서 65533u * 65533u를 곱하면 값이 산출되지만, 캐스트시 uint16_t9를 산출하지만 표준은 uint16_t17 비트에서 32 비트 플랫폼 의 유형 값을 곱할 때 이러한 동작을 요구하지 않습니다 .
— supercat

-1

나는 또한 때때로 C 세계에서 살아남 아야하는 OO 네이티브 (일반적으로 C ++)입니다. 나에게 근본적으로 가장 큰 장애물은 오류 처리 및 리소스 관리를 다루는 것입니다.

C ++에서 우리는 처리 할 수있는 최상위 수준으로 돌아가는 오류를 전달하고 메모리와 기타 리소스를 자동으로 해제하는 소멸자가 있습니다.

많은 C API에는 실제로 구조체에 대한 포인터 인 typedef'd void *를 제공하는 init 함수가 포함되어 있습니다. 그런 다음 이것을 모든 API 호출의 첫 번째 인수로 전달하십시오. 본질적으로 이것이 C ++의 "this"포인터가됩니다. 숨겨져있는 모든 내부 데이터 구조 (매우 OO 개념)에 사용됩니다. 메모리를 관리하는 데 사용할 수도 있습니다. 예를 들어 myapiMalloc이라는 함수를 사용하면 메모리를 mallocs하고이 포인터의 C 버전으로 malloc을 기록하므로 API가 반환 될 때 메모리를 해제 할 수 있습니다. 또한 최근에 발견 한 것처럼 오류 코드를 저장하고 setjmp 및 longjmp를 사용하여 throw catch와 매우 유사한 동작을 제공 할 수 있습니다. 두 개념을 결합하면 C ++ 프로그램의 많은 기능이 제공됩니다.

이제 당신은 C를 C ++로 강제하는 법을 배우고 싶지 않다고 말했습니다. 그것은 내가 (적어도 고의적으로) 묘사하고있는 것이 아닙니다. 이것은 C 기능을 악용하기 위해 간단하게 잘 설계된 방법입니다. 일부 OO 풍미가있는 것으로 나타났습니다. 아마도 OO 언어가 개발 된 이유 일 수 있습니다. 일부 사람들이 모범 사례로 생각한 개념을 공식화 / 강화 / 촉진하는 방법이었습니다.

이것이 당신을 위해 OO 느낌이라고 생각한다면, 대안은 거의 모든 함수가 오류 코드를 반환하도록하는 것입니다. 오류 코드는 종교적으로 모든 함수 호출 후에 확인하고 호출 스택을 전파해야합니다. 각 함수의 끝뿐만 아니라 모든 리턴 포인트 (모든 함수 호출 이후에 계속 될 수 없음을 나타내는 오류를 리턴 할 수 있음) 이후에 모든 자원이 해제되도록해야합니다. 그것은 매우 지루할 수 있으며 잠재적 인 메모리 할당 실패 (또는 파일 읽기 또는 포트 연결 ...)를 처리 할 필요가 없다고 생각하게하는 경향이 있습니다. 이제 "흥미로운"코드를 작성하고 되돌아 와서 오류 처리를 처리하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.