Other answers concentrate on the behavior of -o and they are right, but this is only half of the story. The other half is the behavior of the implicit -print.
In some circumstances find adds an implicit -print to the expression. This happens when the expression contains neither -exec, -ok, nor (explicit) -print nor -print0. Depending on the implementation of find there may be other primaries that suppress the implicit -print (e.g. -execdir). An expression that triggers this behavior makes find convert a command like:
find paths expressioninto:
find paths \( expression \) -print(where backslashes are to protect ( and ) from being interpreted by your shell). The parentheses are there, this is how the implicit -print works.
-name "*.cpp" -o -name "*.h" you used is such expression, so your:
find . -name "*.cpp" -o -name "*.h"is equivalent to:
find . \( -name "*.cpp" -o -name "*.h" \) -printIf not the feature of the implicit -print, your find command without -exec would print nothing at all. It's the implicit -print that makes find print what you expect in this case.
Now if you want to use -exec …instead of the implicit -print then you need to keep the parentheses and type them explicitly:
find . \( -name "*.cpp" -o -name "*.h" \) -exec echo {} \;Your original command with -exec missed these parentheses. This is the moment when the behavior of -o matters. Yes, the parentheses are important when you use -o, this is why you need them in the case with -exec. You don't need to type them in the case without -exec because they appear in the right places automatically due to the implicit -print.