If you haven''t programmed in C++ then I suggest you try it out before making a comparison. Obviously there are far more differences than just "w_move(win, x, y) and win->move(x, y)"
For example, polymorphism - I know I know, it sounds like some stupid catch word used by C++ programmers, but it''s very useful in the right places. The same thing can be done in straight C by using a lookup table, but it''s easier in C++.
Encapsulation - another catch word. You can encapsulate in straight C by only modifying values in a struct through functions, but it''s just not the same as storing private values.
Overloading operators, as has been mentioned. I find that object oriented code is far more reusable than the procedural way of straight C. (Okay, someone is going to flame me for that).
Anyhow, I just wanted to add my 2 cents.
Clay
Classes Vs. Straight Code
quote: Original post by Falagard
For example, polymorphism - I know I know, it sounds like some stupid catch word used by C++ programmers, but it''s very useful in the right places. The same thing can be done in straight C by using a lookup table, but it''s easier in C++.
What *is* polymorphism, anyway? I''m not attacking you or anything--I just want to know. I hear that word a million times a day, but no one seems to know what it means.
quote: I find that object oriented code is far more reusable than the procedural way of straight C. (Okay, someone is going to flame me for that).
All right, I''m going to roast you to cinders for that....
No, I''m just going to say that I believe object orientation doesn''t have to mean C++. I use straight C, but I usually group my data with the functions that operate on the data, in an object-oriented sort of way, and prefix names accordingly (with things like "cl_" for client, and "sv_" for server, and "v_" for view). Why can''t I do that? (No, no! Don''t answer that...I like to do it that way, and I''m not going to stop doing it that way until I''m dead.)
Polymorphism (which is from Greek for "many shapes") means that a member function declared in a base class will be handled differently in a descendent class. In C++, this done via virtual functions. It is important to note that a pointer to a class could refer to an instance of that class, or a descendent class. If we have a base class pointer, and we call a virtual function, at runtime the program will resolve which function in a descendent class to call. This is usually done with a single additional memory reference.
For example:
Suppose I have a base class, CPlayer, and I declare a virtual function for it, GetInput().
Descending from this:
CLocalPlayer, whose input comes from the keyboard, mouse, etc.
CNetworkPlayer, whose input comes in through TCP/IP or UDP packets.
CComputerPlayer, whose input is generated by the program.
I could have a linked list of CPlayer*''s.
For each, I could call pPlayer->GetInput(), and the correct function will be called.
_Properly_ used, polymorphism can simplify your code and make it easier to extend or add new behaviors.
The additional table lookup can result in slower code, but usually, this is not noticeable. If the class type of the instance is known, the compiler can optimize away the virtualness.
For example:
Suppose I have a base class, CPlayer, and I declare a virtual function for it, GetInput().
Descending from this:
CLocalPlayer, whose input comes from the keyboard, mouse, etc.
CNetworkPlayer, whose input comes in through TCP/IP or UDP packets.
CComputerPlayer, whose input is generated by the program.
I could have a linked list of CPlayer*''s.
For each, I could call pPlayer->GetInput(), and the correct function will be called.
_Properly_ used, polymorphism can simplify your code and make it easier to extend or add new behaviors.
The additional table lookup can result in slower code, but usually, this is not noticeable. If the class type of the instance is known, the compiler can optimize away the virtualness.
Good explanation of Polymorphism...
It''s used by declaring a base class, and deriving new classes from that base class and overriding the original class''s functions.
Here''s another example -
I am working on a game that is a mix of 2D and 3D...
A tree on the screen will be just an image, while the player is made from polygons.
I let the user click on anything on the screen to get a description, so the tree and the player both need to be clickable, but they are fundamentally different...
I have a base class called ClickableObject
Both Player and Tree are derived from this..
ClickableObject has a method called IsClicked(x, y)
The Player class overrides this method, and performs a 3d check to see if point x,y on the screen intersects with the polygons of the player, and returns true if so.
The Tree class checks if the x,y coordinate hits on a pixel of the tree that is not transparent... != black and returns true if so.
There are plenty of other uses, like holding an array of all the objects on the screen whether they be 2d or 3D...
It''s nice.
Clay
It''s used by declaring a base class, and deriving new classes from that base class and overriding the original class''s functions.
Here''s another example -
I am working on a game that is a mix of 2D and 3D...
A tree on the screen will be just an image, while the player is made from polygons.
I let the user click on anything on the screen to get a description, so the tree and the player both need to be clickable, but they are fundamentally different...
I have a base class called ClickableObject
Both Player and Tree are derived from this..
ClickableObject has a method called IsClicked(x, y)
The Player class overrides this method, and performs a 3d check to see if point x,y on the screen intersects with the polygons of the player, and returns true if so.
The Tree class checks if the x,y coordinate hits on a pixel of the tree that is not transparent... != black and returns true if so.
There are plenty of other uses, like holding an array of all the objects on the screen whether they be 2d or 3D...
It''s nice.
Clay
Clay LarabieLead DeveloperTacendia.com
Ooh, my name was mentioned
So I guess I''ll have to post in this thread then!
For some ideas on the "tricky" things about writing fast C++, check out the article I wrote on it. ( Boy am I glad I spent a week doing that, it saves me a lot of time! )
Here is a link to the word document.
And as far as the comments here so far go, they are mostly reasonably accurate. I like David M.''s comments, I always have, because he''s someone not generally blinded by the C++ buzzwords, and a very good C programmer from the looks of things. I am a C++ programmer myself, and don''t know all that much about the nice tricky things you could do in C.
I''ve said this before, but I''ll repeat it once again:
Programming languages are tools : tools do not limit your capacity to create cool things, they only help you along. Some people might be fine using what most consider a "cruder" tool, namely C. I don''t think C is necessarily worse than C++, it''s like some people write nicer using ballpoint pens, and some like fountain pens.
I have a knack for Object Oriented programming, it is more natural to me than procedural programming, even though I started with Pascal. That''s why I stick with C++, and why I like it. However, I will NEVER claim to be superior to anyone using another language, simply because of the language difference.
Give me one more medicated peaceful moment..
~ (V)^|) |<é!t|-| ~
ERROR: Your beta-version of Life1.0 has expired. Please upgrade to the full version. All important functions will be disabled from now on.
So I guess I''ll have to post in this thread then!
For some ideas on the "tricky" things about writing fast C++, check out the article I wrote on it. ( Boy am I glad I spent a week doing that, it saves me a lot of time! )
Here is a link to the word document.
And as far as the comments here so far go, they are mostly reasonably accurate. I like David M.''s comments, I always have, because he''s someone not generally blinded by the C++ buzzwords, and a very good C programmer from the looks of things. I am a C++ programmer myself, and don''t know all that much about the nice tricky things you could do in C.
I''ve said this before, but I''ll repeat it once again:
Programming languages are tools : tools do not limit your capacity to create cool things, they only help you along. Some people might be fine using what most consider a "cruder" tool, namely C. I don''t think C is necessarily worse than C++, it''s like some people write nicer using ballpoint pens, and some like fountain pens.
I have a knack for Object Oriented programming, it is more natural to me than procedural programming, even though I started with Pascal. That''s why I stick with C++, and why I like it. However, I will NEVER claim to be superior to anyone using another language, simply because of the language difference.
Give me one more medicated peaceful moment..
~ (V)^|) |<é!t|-| ~
ERROR: Your beta-version of Life1.0 has expired. Please upgrade to the full version. All important functions will be disabled from now on.
It's only funny 'till someone gets hurt.And then it's just hilarious.Unless it's you.
I''d like to make a small addition to the explanation of polymorphism:
The idea behind polymorphism is that you have a similar interface for different behaviours. For example, take a radio and TV. They both have a ''power on'' button. If you push the button on the radio, different things happen than when you push it on the TV.
In C++, polymorphism is supported via the virtual method mechanism. A method call through a pointer (or reference) to a base class, is resolved at runtime to the correct object instance, which can be any object derived from the base class.
Erik
The idea behind polymorphism is that you have a similar interface for different behaviours. For example, take a radio and TV. They both have a ''power on'' button. If you push the button on the radio, different things happen than when you push it on the TV.
In C++, polymorphism is supported via the virtual method mechanism. A method call through a pointer (or reference) to a base class, is resolved at runtime to the correct object instance, which can be any object derived from the base class.
Erik
There is a plethora of myths regarding C++ code (and oddly enough, they''re made by people who really don''t know worth bones about C++.)
David M.: None of your comments regarding the use of classes are true. None of your comments regarding the uselessness of C++ buzzwords are true, either. In fact, most of what everyone said here isn''t true, except the little discussion on polymorphism.
Everyone: If you don''t know what polymorphism is in C++, you haven''t even tapped the surface on how much better it is than C. C++ is even better for C procedural-style programmers because C++ is a better procedural language than C will ever be. I will list cold, hard facts (so get your hanky ready).
(Note: as C++ can handle almost any type of good C code, that makes C++''s feature set at least equal to C''s feature set. I''ll just discuss the features C++ has over and above its C features.)
#1 C++ provides better type-checking
#2 C++ provides operator overloading
#3 C++ provides classes
#4 C++ provides templates
#5 C++ provides polymorphism
#6 C++ provides a huge template library that can handle any data type in a type-safe way (STL).
#7 C++ supports function objects
#8 C++ is not slower than C *
#9 C++ is a better procedural language than C
#10 C++ is a better OOP language than C
#11 C++ is a better generic language than C
(* Of course, when comparing your C code to your C++ code, you have to know how to code both properly. If you don''t know much about C++, that''s like comparing the handwriting ability of your right hand to the handwriting ability of your left hand. Or vice versa. )
Anyone care to argue those things? If not, then I hope you''ll change your mind about C++. Not succumbing to marketing hype is one thing, but closing your mind to new things because of the hype is quite another.
Remember: "Your mind, like a parachute, works best when open." - (unknown)
Now I''ll go through and explain the myths.
If you don''t need the member data, you use static member functions, which are the equivalent of global functions (that C uses).
Example:
file::does_exist works exactly as if I had written a C-style global function like this, except of course you use the scope operator (file::does_exist) when calling the function:
Yes, and it''s much easier. You just have to learn C++ first -- there are too many skeptics who won''t try it more than 5 minutes, and then blame C++ for their own inability. Back to the point though, using OOP in C++ makes it much closer to what is called the "problem domain", which basically means we abstract the problem. Abstraction is the process of wading through the facts and picking out those that are relevant to the current problem.
You can do OOP in C code, but it''s either far more complicated or far less flexible -- especially when trying to mimic features like virtual functions and template classes.
No, it''s not slower. Of course, it depends on what code you are comparing. Poorly written C code may be faster than properly written C++ code, but properly written C code shouldn''t be any faster than properly written C++ code.
No, it just depends on the code being compared.
IMO, that''s wrong. C code is much further than the problem domain and thus is harder to code, all else held equal (some nice little latin phrase I forget...)
Well, your term "over usage of classes" is based on both inexperience and opinion.
IMO, the only thing that should not be in a class is main(). And I would put that in a class if it wasn''t for compiler differences about the implementation. The only thing classes really do is to separate code.
You prefer straight C because that is where you have the most skill (right now).
(multiple instances argument) How often do you need more than one bitmap? How often do you need more than one file? How often do you need more than one string? etc., etc., etc. BTW, if you don''t need multiple instances of a module, consider using a namespace instead of a class. You''d be surprised how many things can be accomplished with them.
(classes are slower argument) Sorry, but in your example, there isn''t any difference. You''re just passing a pointer to the object that you passed anyway with the former.
Well, drop the suspicion and experience it for yourself. Try looking at some C++ code with any attitude other than "bah! I can''t understand, and even if I did it''s too slow". Can''t you see that attitude is not based on facts?
(superior methodologies argument) First, people "babble" about their "superior methodologies" to defend them from other people (who shall remain anonymous) that make outrageously untrue statements about said "superior methodologies". Second, I can give you C++ code out the wazoo -- I always code C++ in classes and don''t use any global functions other than main().
(wallop the snot out of the competition) Now who''s got the "superior methodologies"?
Very few people on this forum actually know what OOP is.
Um...I had to break it to you, but you''re simply emulating C++ classes when you do this:
That''s functionaly equivalent to this:
Now I''ll write a simple program in C++, and I want to see it written just as quickly and easily in C.
Here is the what the program does (for non-C++ programmers):
Well, we''re creating two point objects, and initializing one with the value 100,100 and the other with the value 210,500. Then we create two strings, giving them the values "Hello" and "World!", and then we create another string with the addition of the two.
Then we output the two points and the combined string to the screen, with an endline character after each. Then we output everything to a file.
Of course, this code doesn''t do anything useful, but it does demonstrate how easy it is to write and how readable the code becomes. It took me all of 5 minutes to write that program (and I''m not even a very good programmer, either).
Now write that program in C and post it here! You have 5 minutes starting from...*looks at watch*...now.
- null_pointer
Sabre Multimedia
David M.: None of your comments regarding the use of classes are true. None of your comments regarding the uselessness of C++ buzzwords are true, either. In fact, most of what everyone said here isn''t true, except the little discussion on polymorphism.
Everyone: If you don''t know what polymorphism is in C++, you haven''t even tapped the surface on how much better it is than C. C++ is even better for C procedural-style programmers because C++ is a better procedural language than C will ever be. I will list cold, hard facts (so get your hanky ready).
(Note: as C++ can handle almost any type of good C code, that makes C++''s feature set at least equal to C''s feature set. I''ll just discuss the features C++ has over and above its C features.)
#1 C++ provides better type-checking
#2 C++ provides operator overloading
#3 C++ provides classes
#4 C++ provides templates
#5 C++ provides polymorphism
#6 C++ provides a huge template library that can handle any data type in a type-safe way (STL).
#7 C++ supports function objects
#8 C++ is not slower than C *
#9 C++ is a better procedural language than C
#10 C++ is a better OOP language than C
#11 C++ is a better generic language than C
(* Of course, when comparing your C code to your C++ code, you have to know how to code both properly. If you don''t know much about C++, that''s like comparing the handwriting ability of your right hand to the handwriting ability of your left hand. Or vice versa. )
Anyone care to argue those things? If not, then I hope you''ll change your mind about C++. Not succumbing to marketing hype is one thing, but closing your mind to new things because of the hype is quite another.
Remember: "Your mind, like a parachute, works best when open." - (unknown)
Now I''ll go through and explain the myths.
quote: Original post by farmersckn
But isn''t it just that little bit slower because you have to pass the ''this'' pointer to every method?
If you don''t need the member data, you use static member functions, which are the equivalent of global functions (that C uses).
Example:
class file
{
public:
static bool does_exist(const string& name);
}
bool file::does_exist(const string& name)
{
int temp_handle = fopen(name.c_str(), "a") // I forget the switches
if( temp_handle )
{
fclose(temp_handle);
return true;
}
else
{
return false;
}
}
file::does_exist works exactly as if I had written a C-style global function like this, except of course you use the scope operator (file::does_exist) when calling the function:
bool file_does_exist(const string& name)
{
// insert same code here
}
quote: Original post by farmersckn
Another thing, is it really as easy to write a program using classes as it is to write code like you would in C? Or is it easier?
Yes, and it''s much easier. You just have to learn C++ first -- there are too many skeptics who won''t try it more than 5 minutes, and then blame C++ for their own inability. Back to the point though, using OOP in C++ makes it much closer to what is called the "problem domain", which basically means we abstract the problem. Abstraction is the process of wading through the facts and picking out those that are relevant to the current problem.
You can do OOP in C code, but it''s either far more complicated or far less flexible -- especially when trying to mimic features like virtual functions and template classes.
quote: Original post by SHilbert
1. It''s slower, but by such a small amount it''s virtually negligible.
No, it''s not slower. Of course, it depends on what code you are comparing. Poorly written C code may be faster than properly written C++ code, but properly written C code shouldn''t be any faster than properly written C++ code.
quote: Original post by An Irritable Gent
Impossible to answer. :-)
No, it just depends on the code being compared.
quote: Original post by cow_in_the_well
IMO, if you have well designed C app its just as easy...
IMO, that''s wrong. C code is much further than the problem domain and thus is harder to code, all else held equal (some nice little latin phrase I forget...)
quote: Original post by cow_in_the_well
I know that, and as I said I compile using the CPP compiler in MSVC rather than the C, so i can take advantage of keywords such as new and delete. I just don''t like the over usage of classes some people use. (ie. u don''t need classes on things u don''t need multiinherited (such as the CApp example)).
Well, your term "over usage of classes" is based on both inexperience and opinion.
IMO, the only thing that should not be in a class is main(). And I would put that in a class if it wasn''t for compiler differences about the implementation. The only thing classes really do is to separate code.
quote: Original post by David M.
Well, I don''t know too much about C++, but I prefer straight C. There are some nice things about C++, such as operator overloading and `new'' and `delete'', but I don''t think classes are as useful as everyone makes them out to be. How often would one need multiple instances of a module, anyway? What the heck is the difference between w_move(win, x, y) and win->move(x, y)? Besides the fact that the latter is slower?
You prefer straight C because that is where you have the most skill (right now).
(multiple instances argument) How often do you need more than one bitmap? How often do you need more than one file? How often do you need more than one string? etc., etc., etc. BTW, if you don''t need multiple instances of a module, consider using a namespace instead of a class. You''d be surprised how many things can be accomplished with them.
(classes are slower argument) Sorry, but in your example, there isn''t any difference. You''re just passing a pointer to the object that you passed anyway with the former.
quote: Original post by David M.
I have a sneaking suspicion that all that is the stuff mindless babbling rants are made of, not solid code. So it depends on what your goal is: to babble about your `superior'' methodologies, or to simply wallop the snot out of the competition.
Well, drop the suspicion and experience it for yourself. Try looking at some C++ code with any attitude other than "bah! I can''t understand, and even if I did it''s too slow". Can''t you see that attitude is not based on facts?
(superior methodologies argument) First, people "babble" about their "superior methodologies" to defend them from other people (who shall remain anonymous) that make outrageously untrue statements about said "superior methodologies". Second, I can give you C++ code out the wazoo -- I always code C++ in classes and don''t use any global functions other than main().
(wallop the snot out of the competition) Now who''s got the "superior methodologies"?
quote: Original post by David M.
No, I''m just going to say that I believe object orientation doesn''t have to mean C++. I use straight C, but I usually group my data with the functions that operate on the data, in an object-oriented sort of way, and prefix names accordingly (with things like "cl_" for client, and "sv_" for server, and "v_" for view). Why can''t I do that? (No, no! Don''t answer that...I like to do it that way, and I''m not going to stop doing it that way until I''m dead.)
Very few people on this forum actually know what OOP is.
Um...I had to break it to you, but you''re simply emulating C++ classes when you do this:
struct point
{
int x;
int y;
};
scale(point* scale_point, int factor)
{
scale_point->x += factor;
scale_point->y += factor;
}
That''s functionaly equivalent to this:
struct point
{
int x;
int y;
void scale(int factor);
};
void point::scale(int factor)
{
x += factor;
y += factor;
}
Now I''ll write a simple program in C++, and I want to see it written just as quickly and easily in C.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
struct point
{
point();
point(const int x, const int y);
void scale(const int factor);
int x;
int y;
};
point::point()
: x(0), y(0)
{
}
point::point(const int x, const int y)
: x(x), y(y)
{
}
void point::scale(const int factor)
{
x += factor;
y += factor;
}
inline ostream& operator <<(ostream& os, point data)
{ os << data.x << data.y; return os; }
inline istream& operator >>(istream& is, point data)
{ is >> data.x >> data.y; return is; }
void main()
{
point p1(100, 100);
point p2(210, 500);
p1.scale(5);
p2.scale(100);
string hello("Hello");
string world("World!");
string hello_world = hello + " " + world;
cout << p1 << endl;
cout << p2 << endl;
cout << hello_world << endl;
cout << endl;
fstream file("output.txt", ios::out / ios::binary);
file << p1 << p2 << hello_world;
}
Here is the what the program does (for non-C++ programmers):
Well, we''re creating two point objects, and initializing one with the value 100,100 and the other with the value 210,500. Then we create two strings, giving them the values "Hello" and "World!", and then we create another string with the addition of the two.
Then we output the two points and the combined string to the screen, with an endline character after each. Then we output everything to a file.
Of course, this code doesn''t do anything useful, but it does demonstrate how easy it is to write and how readable the code becomes. It took me all of 5 minutes to write that program (and I''m not even a very good programmer, either).
Now write that program in C and post it here! You have 5 minutes starting from...*looks at watch*...now.
- null_pointer
Sabre Multimedia
Regarding polymorphism: Could be useful. The CLocalPlayer/CComputerPlayer/CNetworkPlayer layout is one way of doing it. But, if I simply had a get_input function pointer in my struct, or something like that, I could change it at run-time, and change the behavior of a player. I know, I know, I hear you: "Who the heck CARES?" I dunno....
Regarding procedural programming: I used to do it this way. Now I do it another way, and darn if it isn''t OOP. I found that my programs were always disorganized and hard to maintain until I got smart and learned how to split up code *right*, simply by making the data central. (Okay, I don''t know if I''m doing it the `right'' way, but it seems to work for me.) For instance, in my silly little `chat room'' client and server (I detest chat rooms, but I wanted to get a feel for Winsock), the client is split up into 6 files: client.c, which contains main(); view.c, which contains the main dialog box; net.c, which connects to the server and handles socket events that the view receives; send.c, which queues packets, making sure they''re sent (it is ran whenever FD_WRITE is received); recv.c, which breaks up the TCP stream into packets, telling me when it has received a full packet (it is ran whenever FD_READ is received); and connect.c, which asks the user for a server address and a nickname. The server is broken up into: server.c, which contains main(); listen.c, which accepts connections; and client.c, which listen.c commands to start threads and handle them. See, everything''s sorta split up according to the data each thing handles: listen.c handles a listening socket, view.c handles a dialog box, send.c handles a queue, etc.
Was that little jab directed at me? :-) ''Cause I was under the impression that OOP simply meant grouping data with the functions that operate on the data--that is, making the data central--which I do. So if I''m mistaken, spare me the insults and educate me properly.
Regarding procedural programming: I used to do it this way. Now I do it another way, and darn if it isn''t OOP. I found that my programs were always disorganized and hard to maintain until I got smart and learned how to split up code *right*, simply by making the data central. (Okay, I don''t know if I''m doing it the `right'' way, but it seems to work for me.) For instance, in my silly little `chat room'' client and server (I detest chat rooms, but I wanted to get a feel for Winsock), the client is split up into 6 files: client.c, which contains main(); view.c, which contains the main dialog box; net.c, which connects to the server and handles socket events that the view receives; send.c, which queues packets, making sure they''re sent (it is ran whenever FD_WRITE is received); recv.c, which breaks up the TCP stream into packets, telling me when it has received a full packet (it is ran whenever FD_READ is received); and connect.c, which asks the user for a server address and a nickname. The server is broken up into: server.c, which contains main(); listen.c, which accepts connections; and client.c, which listen.c commands to start threads and handle them. See, everything''s sorta split up according to the data each thing handles: listen.c handles a listening socket, view.c handles a dialog box, send.c handles a queue, etc.
quote: Original post by null_pointer
Very few people on this forum actually know what OOP is.
Was that little jab directed at me? :-) ''Cause I was under the impression that OOP simply meant grouping data with the functions that operate on the data--that is, making the data central--which I do. So if I''m mistaken, spare me the insults and educate me properly.
"Writing programs based on the concept of an "object" which is a data structure encapsulated with a set of routines, called "methods" which operate on the data. Operations on the data can only be performed via these methods, which are common to all objects which are instances of a particular "class". Thus the interface to objects is well defined, and allows the code implementing the methods to be changed so long as the interface remains the same."
There''s a pretty a good definition. Basically the idea is not only to group code together, but to make it reusable and only provide certain interfaces that allow you to manipulate the data inside the object, hence encapsulation.
Clay
There''s a pretty a good definition. Basically the idea is not only to group code together, but to make it reusable and only provide certain interfaces that allow you to manipulate the data inside the object, hence encapsulation.
Clay
Clay LarabieLead DeveloperTacendia.com
Did anyone here note that when you call a member function ( with the "thiscall" calling convention), the "this" pointer is stored in the ECX register and not on the call stack? That saves you one more push/pop operation to the call stack, and within your member functions, the ECX is never cleared. Note that when you are addressing member variables from within a member function, the addressing mode (i.e. base_pointer + offset (i.e. ECX + member_variable_offset) is the same speed as immediate mode addressing. So, by using a class, I am losing absolutly nothing!
In fact, I''d be willing to bet (although I''ll probably get so flamed for it) that I can write a program using classes in C++ that executes faster than its C language equivalent.
In fact, I''d be willing to bet (although I''ll probably get so flamed for it) that I can write a program using classes in C++ that executes faster than its C language equivalent.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement
Recommended Tutorials
Advertisement