For a link like a.html?b=1. If we want to rewrite it as c.html?d=1. Matching the question mark will be a problem.
Simply

RewriteRule ^a\.html?b=([0-9]+)$          /c.html?d=$1      [L,NC]

Will NOT do.

I've tried many ways to match the question mark, like: ?, \?, [?], [?]{1,1}, [\?] but none will do.

I searched for a while and finally got to know the processing steps:

The first part in RewriteRule is not the URI, but the URI before question mark (call it part A, and call the part after question mark part B).

If Part A is matched, Part A will be replaced by the second part in RewriteRule. Call the replacement Part A2

If Part A2 has a question mark already, then this rule ends. Otherwise, pending B after A2.
So the parameter in the original URI after question mark will have no way to be matched by the first part of RewriteRule.

And here is a solution, use %n instead of $n. $n refers to RewriteRule and %n refers to RewriteCond:

RewriteCond %{QUERY_STRING}      ^b=([0-9]+)$      [NC]
RewriteRule ^a\.html$            /c.html?d=%1      [L,NC]

Hope this will help someone.

Please note that this rule here is only to be in .htaccess. And that's why it is [^a\.html$] but not [^/a\.html$].