It depends how you define OOP. In terms of Java-like OOP where you call methods on objects, procedural programming is pretty much the same. As far as I can tell you can emulate all OOP principles (encapsulation, abstraction, polymorphism, inheritance) in a procedural language like C. Proof of this is GObject, to some extend Objective-C, and many other OOP language implementations using C, like cPython. This is done by using structures and operating on those structures using functions:
typedef struct {
Object *isa;
String *name;
Date *birthday;
} Person;
Person *Person_new();
String *Person_name(Person *self);
void Person_setName(Person *self, String *newName);
// ...
The interface is very OOP like. It doesn't really allow for polymorphism, but it's also possible. It is very similar to a Python interface, except that the attributes are separate from the "methods":
class Person(object):
def __init__(self):
self._name = ""
self._age = datetime.datetime.now()
@property
def name(self):
return self._name
@property
def age(self):
return self._age
I chose Python for the example because "self" is explicit, as in the C example. Many OOP languages, like Java, abstract this.
There are also the Smalltalk-like OOP where messages are sent to objects, rather than calling methods on objects. The difference is subtle at first glance, but it provides a lot of power and flexibility. This can also be implemented in procedural-languages, as proven by Objective-C.
Object-oriented programming is not necessarily a type of language, but rather a paradigm. Object-oriented languages such as Java, Python, Ruby, etc, provide syntactic sugar to easily manipulate objects, and this is the main difference between "procedural languages" and "object-oriented languages".
Indeed, a library, or rather a set of functions operating on a structure, is the same as an object in C++. In fact C++ is implemented in just that way.