So it seems to me that OOP and procedural programming are only different in the way you call a function of an object, but the way you think about a problem and implement its solution is almost the same!
Yes, though only for simple examples. OOP offers capabilities that go further.
First, OOP supports the abstraction of an interface: teasing apart it's usage and its implementation, loosely coupling them, and thereby supporting substitution.
This provides polymorphism, which allows for a certain kind of code reuse: namely, the same client consuming an interface can (assuming liskov) be reused — meaningfully, and without change — with different implementations of the interface.
This is harder to do using a procedural programming style, as the interface and one implementation are inherently tightly coupled together. Something extra is needed to help decouple them, and that would include some OOP techniques.
Second, OOP's inheritance is a mechanism for implementation code reuse. Base class methods can be reused by specializing sub-classes. (Keep in mind the recommendation for composition over inheritance though.)
And those two: separation of interface from implementation, and code inheritance, can work together: a specializing sub-class that can reuse some of the implementation of the base classes, and consuming client code can be reused unmodified with that specialized sub class.
If we write code like this:
Car car = new Car(); // Car is a class here
car.moveForward();
in just two lines of code, we are conflating two different roles. The role played by the second line of code is that of an interface-consuming client, whereas the role played by the first line of code is that of decider, injector, factory, or configurator. Often, we will want to separate these roles, into different modules. Thus, the car might be created in one module, and used by another.
OOP encourages this separation, with the expectation that (when done properly) the consuming client module can perform on multiple potential implementations.
Struct vs. Class is a distinction that depends on the language you're using.
C, of course, doesn't have classes; struct's have by-value semantics until you use pointers. Once you're using pointers, you can apply some OOP techniques.
In C++ there is virtually no difference between struct and class (the access modifier default is different, public for structs, private for classes).
C# has both structs and classes where the former has by-value semantics and the latter by-reference semantics (e.g. local variables & parameters can be structs and can refer to objects, though cannot be objects).
Java and C#'s classes are similar; Java doesn't have structs — its only by-value types are the primitives (e.g. int).
So, in C#, for example, assuming Car
is a class, Car car;
creates a reference variable that can refer to an object that is of type Car
, though also any subclass of Car
. Whereas if Car
is a struct, then that same declaration creates a struct of type Car
.