Compartilhar via


Matrizes (C++)

Uma matriz é uma sequência de objetos do mesmo tipo que ocupam uma área contígua de memória. Matrizes de estilo C tradicionais são a origem de muitos bugs, mas ainda são comuns, especialmente em bases de código mais antigas. No C++ moderno, é altamente recomendável usar std::vector ou std::array em vez de matrizes no estilo C descritas nesta seção. Ambos os tipos de biblioteca padrão armazenam seus elementos como um bloco contíguo de memória. No entanto, eles fornecem maior segurança de tipo e dão suporte a iteradores que têm a garantia de apontar para um local válido dentro da sequência. Para obter mais informações, confira Contêineres.

Declarações de pilha

Em uma declaração de matriz C++, o tamanho da matriz é especificado após o nome da variável, não após o nome do tipo como em alguns outras linguagens. O exemplo a seguir declara uma matriz de 1000 duplos a serem alocados na pilha. O número de elementos deve ser fornecido como um literal inteiro ou então como uma expressão de constante. Isso ocorre porque o compilador precisa saber quanto espaço de pilha alocar; ele não pode usar um valor calculado em tempo de execução. Cada elemento na matriz recebe um valor padrão de 0. Se você não atribuir um valor padrão, cada elemento inicialmente conterá quaisquer valores aleatórios que estejam nesse local de memória.

    constexpr size_t size = 1000;

    // Declare an array of doubles to be allocated on the stack
    double numbers[size] {0};

    // Assign a new value to the first element
    numbers[0] = 1;

    // Assign a value to each subsequent element
    // (numbers[1] is the second element in the array.)
    for (size_t i = 1; i < size; i++)
    {
        numbers[i] = numbers[i-1] * 1.1;
    }

    // Access each element
    for (size_t i = 0; i < size; i++)
    {
        std::cout << numbers[i] << " ";
    }

O primeiro elemento na matriz é o elemento zeroth. O primeiro elemento na matriz é o elemento (n-1), em que n é o número de elementos que a matriz pode conter. O número de elementos na declaração deve ser de um tipo integral e deve ser maior que 0. É sua responsabilidade garantir que seu programa nunca passe um valor para o operador subscrito maior que (size - 1).

Uma matriz de tamanho zero é válida somente quando é o último campo em um struct ou union e quando as extensões da Microsoft estão habilitadas (/Za ou /permissive- não está definido).

Matrizes baseadas em pilha são mais rápidas para alocar e acessar do que matrizes baseadas em heap. No entanto, o espaço na pilha é limitado. O número de elementos de matriz não pode ser tão grande porque usa muita memória de pilha. O quanto é muito depende do seu programa. Você pode usar ferramentas de criação de perfil para determinar se uma matriz é muito grande.

Declarações de heap

Você pode exigir uma matriz muito grande para alocar na pilha ou cujo tamanho não é conhecido no tempo de compilação. É possível alocar essa matriz no heap usando uma expressão new[]. O operador retorna um ponteiro para o primeiro elemento. O operador subscrito funciona na variável de ponteiro da mesma forma que em uma matriz baseada em pilha. Você também pode usar aritmética de ponteiro para mover o ponteiro para quaisquer elementos arbitrários na matriz. É sua responsabilidade garantir que:

  • você sempre mantém uma cópia do endereço de ponteiro original para poder excluir a memória quando não precisar mais da matriz.
  • você não aumenta ou diminui o endereço do ponteiro além dos limites da matriz.

O exemplo a seguir mostra como definir uma matriz no heap no tempo de execução. Ele mostra como acessar os elementos da matriz usando o operador subscrito e usando a aritmética de ponteiro:

void do_something(size_t size)
{
    // Declare an array of doubles to be allocated on the heap
    double* numbers = new double[size]{ 0 };

    // Assign a new value to the first element
    numbers[0] = 1;

    // Assign a value to each subsequent element
    // (numbers[1] is the second element in the array.)
    for (size_t i = 1; i < size; i++)
    {
        numbers[i] = numbers[i - 1] * 1.1;
    }

    // Access each element with subscript operator
    for (size_t i = 0; i < size; i++)
    {
        std::cout << numbers[i] << " ";
    }

    // Access each element with pointer arithmetic
    // Use a copy of the pointer for iterating
    double* p = numbers;

    for (size_t i = 0; i < size; i++)
    {
        // Dereference the pointer, then increment it
        std::cout << *p++ << " ";
    }

    // Alternate method:
    // Reset p to numbers[0]:
    p = numbers;

    // Use address of pointer to compute bounds.
    // The compiler computes size as the number
    // of elements * (bytes per element).
    while (p < (numbers + size))
    {
        // Dereference the pointer, then increment it
        std::cout << *p++ << " ";
    }

    delete[] numbers; // don't forget to do this!

}
int main()
{
    do_something(108);
}

Inicializando matrizes

Você pode inicializar uma matriz em um loop, um elemento por vez ou em uma única instrução. O conteúdo das duas matrizes a seguir é idêntico:

    int a[10];
    for (int i = 0; i < 10; ++i)
    {
        a[i] = i + 1;
    }

    int b[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

Passando matrizes para funções

Quando uma matriz é passada para uma função, ela é passada como um ponteiro para o primeiro elemento, seja uma matriz baseada em pilha ou heap. O ponteiro não contém nenhuma outra informação de tamanho ou tipo. Esse comportamento é chamado de decaimento de ponteiro. Ao passar uma matriz para uma função, você deve sempre especificar o número de elementos em um parâmetro separado. Esse comportamento também implica que os elementos da matriz não são copiados quando a matriz é passada para uma função. Para impedir que a função modifique os elementos, especifique o parâmetro como um ponteiro para elementos const.

O exemplo a seguir mostra uma função que aceita uma matriz e um comprimento. O ponteiro aponta para a matriz original, não uma cópia. Como o parâmetro não é const, a função pode modificar os elementos da matriz.

void process(double *p, const size_t len)
{
    std::cout << "process:\n";
    for (size_t i = 0; i < len; ++i)
    {
        // do something with p[i]
    }
}

Declare e defina o parâmetro de matriz p como const para torná-lo somente leitura dentro do bloco de funções:

void process(const double *p, const size_t len);

A mesma função também pode ser declarada dessas maneiras, sem alteração no comportamento. A matriz ainda é passada como um ponteiro para o primeiro elemento:

// Unsized array
void process(const double p[], const size_t len);

// Fixed-size array. Length must still be specified explicitly.
void process(const double p[1000], const size_t len);

Matrizes multidimensionais

Matrizes construídas a partir de outras matrizes são matrizes multidimensionais. Essas matrizes multidimensionais são especificadas colocando várias expressões de constante entre colchetes em sequência. Por exemplo, considere esta declaração:

int i2[5][7];

Especifica uma matriz do tipo int, organizada conceitualmente em uma matriz bidimensional de cinco linhas e sete colunas, conforme mostrado na figura a seguir:

Layout conceitual de uma matriz multidimensional.

A imagem é uma grade de 7 células de largura e 5 células de altura. Cada célula contém o índice da célula. O primeiro índice de célula é rotulado como 0,0. A próxima célula nessa linha é 0,1 e assim por diante até a última célula nessa linha, que é 0,6. A próxima linha começa com o índice 1,0. A célula depois disso tem um índice de 1,1. A última célula dessa linha é 1,6. Esse padrão se repete até a última linha, que começa com o índice 4,0. A última célula da última linha tem um índice de 4,6.

Você pode declarar matrizes multidimensionadas que têm uma lista de inicializadores (conforme descrito em Inicializadores). Nessas declarações, a expressão de constante que especifica os limites da primeira dimensão pode ser omitida. Por exemplo:

// arrays2.cpp
// compile with: /c
const int cMarkets = 4;
// Declare a float that represents the transportation costs.
double TransportCosts[][cMarkets] = {
   { 32.19, 47.29, 31.99, 19.11 },
   { 11.29, 22.49, 33.47, 17.29 },
   { 41.97, 22.09,  9.76, 22.55 }
};

A declaração anterior define uma matriz com três linhas por quatro colunas. As linhas representam fábricas e colunas representam os mercados para os quais as fábricas enviam. Os valores são os custos de transporte das fábricas para os mercados. A primeira dimensão da matriz é deixada de fora, mas o compilador a preenche examinando o inicializador.

O uso do operador de indireção (*) em um tipo de matriz n-dimensional gera uma matriz dimensional n-1. Se n for 1, um escalar (ou elemento de matriz) será produzido.

As matrizes de C++ são armazenadas na ordem de linha principal. Ordem de linha principal significa que o último subscrito varia mais rápido.

Exemplo

Você também pode omitir a especificação de limites para a primeira dimensão de uma matriz multidimensional em declarações de função, conforme mostrado aqui:

// multidimensional_arrays.cpp
// compile with: /EHsc
// arguments: 3
#include <limits>   // Includes DBL_MAX
#include <iostream>

const int cMkts = 4, cFacts = 2;

// Declare a float that represents the transportation costs
double TransportCosts[][cMkts] = {
   { 32.19, 47.29, 31.99, 19.11 },
   { 11.29, 22.49, 33.47, 17.29 },
   { 41.97, 22.09,  9.76, 22.55 }
};

// Calculate size of unspecified dimension
const int cFactories = sizeof TransportCosts /
                  sizeof( double[cMkts] );

double FindMinToMkt( int Mkt, double myTransportCosts[][cMkts], int mycFacts);

using namespace std;

int main( int argc, char *argv[] ) {
   double MinCost;

   if (argv[1] == 0) {
      cout << "You must specify the number of markets." << endl;
      exit(0);
   }
   MinCost = FindMinToMkt( *argv[1] - '0', TransportCosts, cFacts);
   cout << "The minimum cost to Market " << argv[1] << " is: "
       << MinCost << "\n";
}

double FindMinToMkt(int Mkt, double myTransportCosts[][cMkts], int mycFacts) {
   double MinCost = DBL_MAX;

   for( size_t i = 0; i < cFacts; ++i )
      MinCost = (MinCost < TransportCosts[i][Mkt]) ?
         MinCost : TransportCosts[i][Mkt];

   return MinCost;
}
The minimum cost to Market 3 is: 17.29

A função FindMinToMkt é escrita de forma que a adição de novas fábricas não exija qualquer alteração de código, somente uma recompilação.

Inicializando matrizes

Matrizes de objetos que têm um construtor de classe são inicializadas pelo construtor. Quando houver menos itens na lista de inicializadores do que elementos na matriz, o construtor padrão será usado para os elementos restantes. Se nenhum construtor padrão for definido para a classe, a lista de inicializadores deverá estar completa, ou seja, deve haver um inicializador para cada elemento na matriz.

Considere a classe Point que define dois construtores:

// initializing_arrays1.cpp
class Point
{
public:
   Point()   // Default constructor.
   {
   }
   Point( int, int )   // Construct from two ints
   {
   }
};

// An array of Point objects can be declared as follows:
Point aPoint[3] = {
   Point( 3, 3 )     // Use int, int constructor.
};

int main()
{
}

O primeiro elemento de aPoint é construído usando o construtor Point( int, int ); os dois elementos restantes são construídos com o construtor padrão.

As matrizes de membros estáticos (seja const ou não) podem ser inicializadas em suas definições (fora da declaração de classe). Por exemplo:

// initializing_arrays2.cpp
class WindowColors
{
public:
    static const char *rgszWindowPartList[7];
};

const char *WindowColors::rgszWindowPartList[7] = {
    "Active Title Bar", "Inactive Title Bar", "Title Bar Text",
    "Menu Bar", "Menu Bar Text", "Window Background", "Frame"   };
int main()
{
}

Acessando elementos de matriz

Você pode acessar elementos individuais de uma matriz usando o operador de subscrito de matriz ([ ]). Se você usar o nome de uma matriz unidimensional sem um subscrito, ele será avaliado como um ponteiro para o primeiro elemento da matriz.

// using_arrays.cpp
int main() {
   char chArray[10];
   char *pch = chArray;   // Evaluates to a pointer to the first element.
   char   ch = chArray[0];   // Evaluates to the value of the first element.
   ch = chArray[3];   // Evaluates to the value of the fourth element.
}

Ao usar matrizes multidimensionais, você pode usar diversas combinações em expressões.

// using_arrays_2.cpp
// compile with: /EHsc /W1
#include <iostream>
using namespace std;
int main() {
   double multi[4][4][3];   // Declare the array.
   double (*p2multi)[3];
   double (*p1multi);

   cout << multi[3][2][2] << "\n";   // C4700 Use three subscripts.
   p2multi = multi[3];               // Make p2multi point to
                                     // fourth "plane" of multi.
   p1multi = multi[3][2];            // Make p1multi point to
                                     // fourth plane, third row
                                     // of multi.
}

No código acima, multi é uma matriz tridimensional do tipo double. O ponteiro p2multi aponta para uma matriz do tipo double de tamanho três. Nesse exemplo, a matriz é usada com um, dois e três subscritos. Embora seja mais comum especificar todos os subscritos, como na instrução cout, às vezes, é útil selecionar um subconjunto específico de elementos da matriz, como mostrado nas instruções que se seguem cout.

Sobrecarregando o operador subscrito

Como outros operadores, o operador subscrito ([]) pode ser redefinido pelo usuário. O comportamento padrão do operador subscrito, se não sobrecarregado, é combinar o nome da matriz e o subscrito usando o seguinte método:

*((array_name) + (subscript))

Como em qualquer adição que envolve tipos do ponteiro, a escala é executada automaticamente para se ajustar ao tamanho do tipo. O valor resultante não é n bytes da origem de array_name; em vez disso, é o elemento nth da matriz. Para obter mais informações sobre essa conversão, consulte Operadores aditivos.

De maneira semelhante, para matrizes multidimensionais, o endereço é derivado usando o seguinte método:

((array_name) + (subscript1 * max2 * max3 * ... * maxn) + (subscript2 * max3 * ... * maxn) + ... + subscriptn))

Matrizes em expressões

Quando um identificador de um tipo de matriz aparece em uma expressão que não seja sizeof, address-of (&) ou a inicialização de uma referência, ele é convertido em um ponteiro para o primeiro elemento da matriz. Por exemplo:

char szError1[] = "Error: Disk drive not ready.";
char *psz = szError1;

O ponteiro psz aponta para o primeiro elemento da matriz szError1. Matrizes, ao contrário dos ponteiros, não são valores l modificáveis. É por isso que a atribuição a seguir é inválida:

szError1 = psz;

Confira também

std::array