Voltando ao nosso texto de tópicos passados:
abcdefghijklmnopqurtuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
bastter.com
blablabla blabla bla
21-12345-5678
31*12345*1234
(45)3333-6666
Já vimos como pegar caracteres alfanuméricos, numéricos, como usar âncoras, etc. Mas até agora só vimos como dar match em um único caractere numérico, ou um único caractere alfanumérico.
No texto acima temos 3 exemplos de escrita de um telefone em um texto. Vimos como dar match em caracteres numéricos usando \d, mas não vimos como pegar um exemplo inteiro tal qual um número de telefone ou um número de CPF.
Relembrando que \d dá match em caracteres numéricos e que o "." dá match em tudo, uma possível expressão regular para pegar os dois primeiros exemplos de número de telefone seria:
\d\d.\d\d\d\d\d.\d\d\d\d
Notem que na expressão regular acima nós temos a busca por dois caracteres numéricos, seguida pela busca de qualquer tipo de caractere por meio do ponto, seguida pela busca de 5 caracteres numéricos em sequência, seguida pela busca de qualquer caractere por meio de outro ponto, seguida pela busca de 4 caracteres numéricos.
Rodando a regex acima em nosso texto:

Vejam que consegui casar com os dois números de telefones que apresentavam esse tipo de formato.
Para pegar o outro número de telefone, basta eu tirar um \d da segunda sequência de "\d's", e adicionar um ponto ao início para pegar o primeiro parêntese:
.\d\d.\d\d\d\d.\d\d\d\d
Como resultado temos:

Mas digamos que eu tenho um banco de dados gigantesco num documento de texto e as possibilidades de números são essas duas possibilidades. Imaginem que nosso banco tem dados assim:
21-12345-5678
31*12345*1234
(45)3333-6666
35-55555-4444
45*66666*1111
89-12345-1234
(51)5555-6666
(11)5105-6262
65-98765-4321
21-12345-5678
31*12345*1234
(45)3333-6666
35-55555-4444
45*66666*1111
89-12345-1234
(51)5555-6666
(11)5105-6262
65-98765-4321
21-12345-5678
31*12345*1234
(45)3333-6666
35-55555-4444
45*66666*1111
89-12345-1234
(51)5555-6666
(11)5105-6262
65-98765-4321
21-12345-5678
31*12345*1234
(45)3333-6666
35-55555-4444
45*66666*1111
89-12345-1234
(51)5555-6666
(11)5105-6262
65-98765-4321
E eu quero capturar todos os números de telefone possíveis. Percebemos que uma regex pega um dos tipos e a outra regex pega outro dos tipos.
E a diferença entre as duas regex é o primeiro caractere não numérico (parêntese) no segundo caso, e um dígito numérico a mais no primeiro caso:
# 1ª regex
\d\d.\d\d\d\d\d.\d\d\d\d
# 2ª regex
.\d\d.\d\d\d\d.\d\d\d\d
Como eu faço pra pegar todas as possibilidades? Passando duas verificações com duas regex diferentes?
Não, não preciso disso. Existe um caractere especial em regex que é o ponto de interrogação (?).
O ? funciona meio como a busca opcional pelo caractere que está antes dele. Se ele achar algo, o match irá mostrar o que vem antes do ?. Se ele não achar nada, o resto da regex é verificado normalmente.
No frigir dos ovos, o que ele verifica é a possibilidade do que está antes dele.
E qual a utilidade dele no nosso problema? Nós temos a primeira ocorrência de um caractere que não é numérico (um parêntese) que só existe em telefones desse tipo:
(51)5555-6666
E nos outros tipos de telefone nós temos um dígito a mais:
35-55555-4444
45*66666*1111
Então eu preciso que na minha regex seja opcional eu achar algum caractere não numérico na primeira posição, além de ser opcional achar um quinto dígito numérico no segundo conjunto de caracteres numéricos.
Dessa forma, minha regex seria algo assim:
.?\d\d.\d\d\d\d\d?.\d\d\d\d
Notem o primeiro ponto de interrogação, que verifica a possibilidade da existência de qualquer coisa no início, e o segundo ponto de interrogação, que verifica a possibilidade de existência de um quinto dígito numérico no segundo conjunto de dígitos. Verificando a regex acima em nosso banco de dados de telefones:

Conseguimos pegar todas as possibilidades de números telefônicos em nossos dados.
Mas tem uma coisa estranha na minha regex que é a grande repetição de caracteres \d. Em programação, se algo está repetitivo pode estar muito errado.
Eu posso trocar o \d\d\d\d\d pelo \d{5}. Ou seja, um número dentro de chaves em regex é um quantificador. Nesse caso, é um quantificador de dígitos numéricos. Sendo assim, posso trocar isto:
.?\d\d.\d\d\d\d\d?.\d\d\d\d
Por isto:
.?\d{2}.\d{4}\d?.\d{4}
Ficou bem mais limpo. E eu posso até apagar meu segundo ? fazendo assim:
.?\d{2}.\d{4,5}.\d{4}
Ou seja, em \d{4,5} eu verifico a existência de números entre 4 e 5 possibilidades. Então nem preciso mais do "?" aí.
Dessa forma, com minha regex turbinada e sem tantas repetições eu consigo dar match em todos os telefones:

Ainda dá pra simplificar mais ainda minha regex? Dá sim!
Notem que temos um padrão na nossa regex acima. A busca por um ponto, seguida por dígitos numéricos, por outro ponto, por mais dígitos numéricos e por mais um ponto com mais dígitos numéricos depois:
# ponto -> dígitos -> ponto -> dígitos -> ponto -> dígitos
.?\d{2}.\d{4,5}.\d{4}
Então eu posso agrupar esse padrão dessa forma:
(.?\d{2,5})+
O grupo é representado pelo que está dentro dos parênteses. Dentro do grupo, temos a ocorrência ou não de um ponto, seguida pela ocorrência de dígitos que podem variar de 2 a 5 dígitos. Por fim, ao fechar o parêntese e adicionar um sinal de + eu busco por esse grupo uma ou mais vezes em meu texto.
Rodando a regex acima em nosso banco de dados:

Temos os mesmos 36 matches de sempre.
Notem a lindeza que é regex. Transformamos isto:
.?\d\d.\d\d\d\d\d?.\d\d\d\d
Nisto:
(.?\d{2,5})+
E obtemos o mesmo resultado.
Acabei indo além do que pretendia neste tópico. A intenção era só mostrar como casar com todos os números de telefone mas à medida que a gente vai conseguindo vai tentando deixar a regex mais bonita e não consegui não postar esse caminho até o fim. Mas ainda vou falar de quantificadores, detalhar mais possibilidades com quantificadores tais como "{5}", "?" e "+", então não se desesperem.