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.