Anteriormente, eu usava apenas linguagens de programação orientada a objetos (C ++, Ruby, Python, PHP) e agora estou aprendendo C. Estou tendo dificuldade em descobrir a maneira correta de fazer as coisas em uma linguagem sem o conceito de 'Objeto'. Percebo que é possível usar paradigmas de POO em C, mas eu gostaria de aprender a maneira C-idiomática.
Ao resolver um problema de programação, a primeira coisa que faço é imaginar um objeto que resolva o problema. Com quais etapas eu substituo isso ao usar um paradigma de Programação Imperativa não OOP?
object-oriented
c
Mas Bagol
fonte
fonte
qux = foo.bar(baz)
se tornamqux = Foo_bar(foo, baz)
.Respostas:
struct
.É isso aí.
Como você escreveu uma aula? É assim que você escreve um arquivo .C. Concedido, você não obtém coisas como polimorfismo e herança de método, mas pode simular aqueles com diferentes nomes de funções e composição .
Para pavimentar o caminho, estude Programação Funcional. É realmente incrível o que você pode fazer sem aulas, e algumas coisas realmente funcionam melhor sem a sobrecarga das aulas.
Leitura adicional
Orientação a objetos em ANSI C
fonte
typedef
issostruct
e criar algo parecido com classe .typedef
tipos e -ed podem ser incluídos em outrosstruct
s que podem sertypedef
-ed. o que você não obtém com C é a sobrecarga do operador e a herança superficialmente simples das classes e dos membros contidos no C ++. e você não recebe muitas sintaxes estranhas e não naturais que obtém com o C ++. Eu realmente amo o conceito de POO, mas acho que C ++ é uma realização feia de POO. Eu gosto de C porque é um idioma menor e deixa de fora a sintaxe do idioma que é melhor deixar para funções.a lot
of things actually work better without the overhead of classes
a lot of things actually work better with addition of class-based OOP
. Fonte: TypeScript, Dart, CoffeeScript e todas as outras maneiras pelas quais a indústria está tentando se afastar de uma linguagem OOP funcional / protótipo.Leia o SICP e aprenda Scheme, além da idéia prática de tipos de dados abstratos . Então codificar em C é fácil (já que com SICP, um pouco de C e um pouco de PHP, Ruby, etc ... seu pensamento seria bastante amplo e você entenderia que a programação orientada a objetos pode não ser o melhor estilo em todos os casos, mas apenas para algum tipo de programa). Tenha cuidado com a alocação de memória dinâmica C , que provavelmente é a parte mais difícil. O padrão da linguagem de programação C99 ou C11 e sua biblioteca padrão C são na verdade bastante pobres (ele não conhece TCP ou diretórios!), E muitas vezes você precisará de algumas bibliotecas ou interfaces externas (por exemplo,POSIX , libcurl para a biblioteca do cliente HTTP, libonion para a biblioteca do servidor HTTP, GMPlib para bignums, alguma biblioteca como libunistring para UTF-8, etc ...).
Seus "objetos" geralmente estão em C - alguns relacionados
struct
- e você define o conjunto de funções operando neles. Para funções curtas ou muito simples, considere defini-las, com o relevantestruct
, comostatic inline
em algum arquivo de cabeçalho,foo.h
para estar#include
-d em outro lugar.Observe que a programação orientada a objetos não é o único paradigma de programação . Em algumas ocasiões, outros paradigmas valem a pena ( programação funcional à Ocaml ou Haskell ou mesmo Scheme ou Commmon Lisp, programação lógica à Prolog, etc etc ... Leia também o blog de J.Pitrat sobre inteligência artificial declarativa). Veja o livro de Scott: Pragmática da Linguagem de Programação
Na verdade, um programador em C ou Ocaml geralmente não deseja codificar em um estilo de programação orientado a objetos. Não há razão para se forçar a pensar em objetos quando isso não é útil.
Você definirá algumas
struct
e as funções que operam nelas (geralmente através de ponteiros). Você pode precisar de algumas uniões marcadas (geralmente umastruct
com um membro de tag, muitas vezes algumasenum
e algumasunion
internas), e pode ser útil ter um membro de matriz flexível no final de alguns dos seusstruct
-s.Olhe dentro do código fonte de alguns softwares livres existentes em C (consulte o github & sourceforge para encontrar alguns). Provavelmente, a instalação e o uso de uma distribuição Linux seria útil: ela é feita quase apenas de software livre, possui ótimos compiladores C de software livre ( GCC , Clang / LLVM ) e ferramentas de desenvolvimento. Consulte também Programação avançada do Linux se você deseja desenvolver para Linux.
Não se esqueça de compilar com todos os avisos e informações de depuração, por exemplo -
gcc -Wall -Wextra -g
notavelmente durante as fases de desenvolvimento e depuração - e aprender a usar algumas ferramentas, por exemplo, valgrind para detectar vazamentos de memória , ogdb
depurador, etc. Lembre-se de entender bem o que é indefinido comportamento e evite-o fortemente (lembre-se de que um programa pode ter algum UB e às vezes parece "funcionar").Quando você realmente precisa de construções orientadas a objetos (em particular herança ), pode usar ponteiros para estruturas e funções relacionadas. Você poderia ter seu próprio maquinário de tabela , ter cada "objeto" começando com um ponteiro para um
struct
ponteiro de função contendo. Você aproveita a capacidade de converter um tipo de ponteiro para outro tipo de ponteiro (e do fato de poder converter a partir de um questruct super_st
contenha os mesmos tipos de campo que aqueles que iniciam umstruct sub_st
para emular herança). Observe que C é suficiente para implementar sistemas de objetos bastante sofisticados - em particular seguindo algumas convenções -, como o GObject (do GTK / Gnome) demonstra.Quando você realmente precisa de fechamentos , geralmente os emula com retornos de chamada , com a convenção de que todas as funções que usam um retorno de chamada passam por um ponteiro de função e por alguns dados do cliente (consumidos pelo ponteiro de função quando chama isso). Você também pode ter (convencionalmente) seu próprio fechamento-como-
struct
s (contendo algum ponteiro de função e os valores fechados).Como C é uma linguagem de nível muito baixo, é importante definir e documentar suas próprias convenções (inspiradas pela prática em outros programas C), em particular sobre gerenciamento de memória e, provavelmente, algumas convenções de nomenclatura. É útil ter uma idéia sobre a arquitetura do conjunto de instruções . Não se esqueça que um compilador C pode fazer muitas otimizações no seu código (se você pedir), então não se importe muito em fazer micro-otimizações manualmente, deixe isso para o seu compilador (
gcc -Wall -O2
para compilação otimizada de Programas). Se você se preocupa com benchmarking e desempenho bruto, deve habilitar otimizações (depois que o programa for depurado).Não se esqueça que às vezes a metaprogramação é útil . Frequentemente, grandes softwares escritos em C contêm alguns scripts ou programas ad-hoc para gerar algum código C usado em outro lugar (e você também pode executar alguns truques sujos do pré-processador C , por exemplo , macros X ). Existem alguns geradores de programas C úteis (por exemplo, yacc ou gnu bison para gerar analisadores, gperf para gerar funções de hash perfeitas, etc ...). Em alguns sistemas (principalmente Linux e POSIX), você pode até gerar algum código C em tempo de execução no
generated-001.c
arquivo, compilá-lo em um objeto compartilhado executando algum comando (comogcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so
) em tempo de execução, carregar dinamicamente esse objeto compartilhado usando dlopen& obtenha um ponteiro de função de um nome usando dlsym . Estou fazendo esses truques no MELT (uma linguagem específica de domínio semelhante ao Lisp que pode ser útil para você, pois permite a personalização do compilador GCC ).Esteja ciente dos conceitos e técnicas de coleta de lixo (a contagem de referência geralmente é uma técnica para gerenciar a memória em C, e é uma forma ruim de coleta de lixo que não lida bem com referências circulares ; você pode ter indicadores fracos para ajudar nisso, mas pode ser complicado). Em algumas ocasiões, você pode considerar usar o coletor de lixo conservador de Boehm .
fonte
A maneira como o programa é construído é basicamente definindo quais ações (funções) devem ser executadas para resolver o problema (é por isso que é chamado de linguagem processual). Cada ação corresponderá a uma função. Em seguida, você precisa definir que tipo de informação cada função receberá e quais informações precisam retornar.
O programa é normalmente separado em arquivos (módulos). Cada arquivo normalmente possui um grupo de funções relacionadas. No início de cada arquivo, você declara (fora de qualquer função) variáveis que serão usadas por todas as funções nesse arquivo. Se você usar o qualificador "estático", essas variáveis serão visíveis apenas dentro desse arquivo (mas não de outros arquivos). Se você não usar o qualificador "estático" em variáveis definidas fora das funções, elas também estarão acessíveis a partir de outros arquivos e esses outros arquivos deverão declarar a variável como "externa" (mas não defini-la) para que o compilador as procure. em outros arquivos.
Então, resumindo, você primeiro pensa nos procedimentos (funções) e depois garante que todas as funções tenham acesso às informações necessárias.
fonte
As APIs C frequentemente - talvez até mesmo, geralmente - têm uma interface essencialmente orientada a objetos se você as observar da maneira certa.
Em C ++:
Em C:
Como você deve saber, no C ++ e em várias outras linguagens OO formais, um método de objeto usa um primeiro argumento que é um ponteiro para o objeto, como a versão C
bar()
acima. Para um exemplo de onde isso vem à tona no C ++, considere comostd::bind
pode ser usado para ajustar os métodos de objeto às assinaturas de funções:Como outros já apontaram, a diferença real é que linguagens formais de OO podem implementar polimorfismo, controle de acesso e vários outros recursos interessantes. Mas a essência da programação orientada a objetos, a criação e manipulação de estruturas de dados complexas e discretas, já é uma prática fundamental em C.
fonte
Uma das grandes razões pelas quais as pessoas são incentivadas a aprender C é que é uma das mais baixas das linguagens de programação de alto nível. As linguagens OOP facilitam o pensamento sobre modelos de dados e modelagem de código e passagem de mensagens, mas no final do dia, um microprocessador executa o código passo a passo, pulando dentro e fora dos blocos de código (funções em C) e movendo-se referências a variáveis (ponteiros em C) para que diferentes partes de um programa possam compartilhar dados. Pense em C como linguagem assembly em inglês - fornecendo instruções passo a passo para o microprocessador do seu computador - e você não errará muito. Como bônus, a maioria das interfaces de sistema operacional funciona como chamadas de função C em vez de paradigmas OOP,
fonte
uint16_t blah(uint16_t x) {return x*x;}
funcionará de forma idêntica em máquinas comunsigned int
16 bits ou 33 bits ou mais. Alguns compiladores para máquinas comunsigned int
17 a 32 bits, no entanto, podem considerar uma chamada para esse método ... #uint16_t
, renderia 9, o Padrão não determina tais comportamentos ao multiplicar valores do tipouint16_t
em plataformas de 17 a 32 bits.Também sou nativo de OO (C ++ em geral) que às vezes precisa sobreviver em um mundo de C. Para mim, o maior obstáculo fundamental é lidar com o tratamento de erros e o gerenciamento de recursos.
Em C ++, lançamos para repassar um erro de onde ocorre todo o caminho de volta ao nível superior, onde podemos lidar com isso e temos destruidores para liberar automaticamente nossa memória e outros recursos.
Você pode notar que muitas APIs C incluem uma função init que fornece um void * digitado, que é realmente um ponteiro para uma estrutura. Em seguida, você passa isso como o primeiro argumento para cada chamada de API. Essencialmente, isso se torna o ponteiro "this" do C ++. Ele é usado para todas as estruturas de dados internas que estão ocultas (um conceito muito OO). Você também pode usá-lo para gerenciar memória, por exemplo, ter uma função chamada myapiMalloc, que aloca sua memória e grava o malloc na versão C de um ponteiro deste, para garantir que ele seja liberado quando a API retornar. Além disso, como descobri recentemente, você pode usá-lo para armazenar códigos de erro e usar setjmp e longjmp para fornecer um comportamento muito semelhante ao throw catch. A combinação dos dois conceitos oferece muitas funcionalidades de um programa C ++.
Agora você disse que não queria aprender a forçar C em C ++. Não é exatamente isso que estou descrevendo (pelo menos não deliberadamente). Este é simplesmente um método (esperançosamente) bem projetado para explorar a funcionalidade C. Ele acaba tendo alguns sabores de OO - talvez seja por isso que as linguagens de OO se desenvolveram, elas foram uma maneira de formalizar / reforçar / facilitar conceitos que algumas pessoas consideraram melhores práticas.
Se você acha que esse é o ponto de partida para você, a alternativa é que praticamente todas as funções retornem um código de erro que você deve religiosamente garantir que verifique após cada chamada de função e propague a pilha de chamadas. Você deve garantir que todos os recursos sejam liberados não apenas no final de cada função, mas em todos os pontos de retorno (que podem ocorrer após qualquer chamada de função que possa retornar um erro que indica que você não pode continuar). Pode ser muito tedioso e fazer com que você pense que provavelmente não preciso lidar com essa falha potencial de alocação de memória (ou leitura de arquivo ou conexão de porta ...), apenas assumirei que funcionará ou eu ' Vou escrever o código "interessante" agora e voltar e lidar com o tratamento de erros - o que nunca acontece.
fonte