close up photo of programming of codes

codecungnhau.com

Một trang web về kỹ thuật lập trình

Khi nào chúng ta sử dụng Danh sách Khởi tạo (Initializer list) trong C++?

Danh sách khởi tạo được sử dụng để khởi tạo các thành viên dữ liệu của một lớp. Danh sách các thành viên sẽ được khởi tạo được chỉ ra với hàm tạo là một danh sách được phân tách bằng dấu phẩy, theo sau là dấu hai chấm. Sau đây là một ví dụ sử dụng danh sách khởi tạo để khởi tạo x và y của lớp Point.

#include<iostream>
using namespace std;
 
class Point {
private:
    int x;
    int y;
public:
    Point(int i = 0, int j = 0):x(i), y(j) {}
    /*  Danh sách khởi tạo trên là tuỳ chọn vì
        vì hàm tạo cũng có thể viết như sau:
        Point(int i = 0, int j = 0) {
            x = i;
            y = j;
        }
    */   
     
    int getX() const {return x;}
    int getY() const {return y;}
};
 
int main() {
  Point t1(10, 15);
  cout<<"x = "<<t1.getX()<<", ";
  cout<<"y = "<<t1.getY();
  return 0;
}
 
/* OUTPUT:
   x = 10, y = 15
*/

Đoạn mã trên chỉ là một ví dụ cho cú pháp của danh sách khởi tạo. Trong đoạn mã trên, x và y cũng có thể được khởi tạo dễ dàng bên trong hàm tạo. Nhưng có những tình huống mà việc khởi tạo các thành viên dữ liệu bên trong hàm tạo không hoạt động và phải sử dụng Danh sách khởi tạo. Sau đây là những trường hợp như vậy:

Để khởi tạo các thành viên dữ liệu hằng không tĩnh (non-static const)

Các thành viên dữ liệu const phải được khởi tạo bằng Danh sách khởi tạo. Trong ví dụ sau, “t” là một thành viên dữ liệu const của lớp Test và được khởi tạo bằng Danh sách khởi tạo. Lý do khởi tạo thành viên dữ liệu const trong danh sách khởi tạo là vì không có bộ nhớ nào được cấp phát riêng cho thành viên dữ liệu const, nó được tạo lại trong bảng ký hiệu symbol table do đó chúng ta cần khởi tạo nó trong danh sách khởi tạo.
Ngoài ra, nó là một hàm tạo có tham số và chúng ta không cần gọi toán tử gán, có nghĩa là chúng ta đang tránh một thao tác bổ sung.

#include<iostream>
using namespace std;
 
class Test {
    const int t;
public:
    Test(int t):t(t) {}  //Phải dùng danh sách khởi tạo
    int getT() { return t; }
};
 
int main() {
    Test t1(10);
    cout<<t1.getT();
    return 0;
}
 
/* OUTPUT:
   10
*/

Để khởi tạo các thành viên tham chiếu

Các thành viên tham chiếu phải được khởi tạo bằng Danh sách khởi tạo. Trong ví dụ sau, “t” là một thành viên tham chiếu của lớp Test và được khởi tạo bằng Danh sách khởi tạo.

#include<iostream>
using namespace std;
 
class Test {
    int &t;
public:
    Test(int &t):t(t) {}  //Phải dùng danh sách khởi tạo
    int getT() { return t; }
};
 
int main() {
    int x = 20;
    Test t1(x);
    cout<<t1.getT()<<endl;
    x = 30;
    cout<<t1.getT()<<endl;
    return 0;
}
/* OUTPUT:
    20
    30
 */

Để khởi tạo các đối tượng thành viên không có hàm tạo mặc định

Trong ví dụ sau, một đối tượng “a” của lớp “A” là thành viên dữ liệu của lớp “B” và “A” không có hàm tạo mặc định. Danh sách khởi tạo phải được sử dụng để khởi tạo “a”.

#include <iostream>
using namespace std;
 
class A {
    int i;
public:
    A(int );
};
 
A::A(int arg) {
    i = arg;
    cout << "A's Constructor called: Value of i: " << i << endl;
}
 
// Lớp B. chứa đối tượng thuộc lớp A
class B {
    A a;
public:
    B(int );
};
 
B::B(int x):a(x) {  //Phải dùng danh sách khởi tạo
    cout << "B's Constructor called";
}
 
int main() {
    B obj(10);
    return 0;
}
/* OUTPUT:
    A's Constructor called: Value of i: 10
    B's Constructor called
*/

Nếu lớp A có cả hàm tạo mặc định và được tham số hóa, thì Danh sách khởi tạo là không bắt buộc nếu chúng ta muốn khởi tạo “a” bằng cách sử dụng hàm tạo mặc định, nhưng là bắt buộc khi khởi tạo “a” bằng cách sử dụng hàm tạo được tham số hóa.

Để khởi tạo các thành viên lớp cơ sở

Giống như điểm trên, hàm tạo được tham số hóa của lớp cơ sở chỉ có thể được gọi bằng cách sử dụng Danh sách khởi tạo.

#include <iostream>
using namespace std;
 
class A {
    int i;
public:
    A(int );
};
 
A::A(int arg) {
    i = arg;
    cout << "A's Constructor called: Value of i: " << i << endl;
}
 
// Lớp B thừa kế từ A
class B: A {
public:
    B(int );
};
 
B::B(int x):A(x) { //Phải dùng danh sách khởi tạo
    cout << "B's Constructor called";
}
 
int main() {
    B obj(10);
    return 0;
}

Khi tên tham số của hàm tạo giống với thành viên dữ liệu

Nếu tên tham số của hàm tạo giống với tên thành viên dữ liệu thì thành viên dữ liệu phải được khởi tạo bằng cách sử dụng con trỏ thí hoặc Danh sách khởi tạo. Trong ví dụ sau, cả tên thành viên và tên tham số cho A() đều là “i”.

#include <iostream>
using namespace std;
 
class A {
    int i;
public:
    A(int );
    int getI() const { return i; }
};
 
A::A(int i):i(i) { }  // Phải dùng con trỏ this hoặc danh sách khởi tạo
/* Hàm tạo trên có thể viết lại như sau:
A::A(int i) {
    this->i = i;
}
*/
 
int main() {
    A a(10);
    cout<<a.getI();
    return 0;
}
/* OUTPUT:
    10
*/

Vì lý do hiệu suất

// Không dùng Danh sách khởi tạo
class MyClass {
    Type variable;
public:
    MyClass(Type a) {  //Giả sử rằng Type là một lớp đã
                     // đã được khai báo với các hàm
                     // và toán tử thích hợp.
      variable = a;
    }
};

Dưới đây là những gì mà trình biên dịch thực hiện để tạo đối tượng MyClass:

  1. Hàm tạo của Type được gọi cho đối tượng a;
  2. Toán tử gán của Type sẽ được gọi bên trong hàm tạo MyClass để gán variable = a;
  3. Hàm huỷ của Type được gọi cho đối tượng a vì nó đã ra khỏi hàm MyClass.

Bây giờ hãy xem xét cùng một đoạn mã với hàm tạo MyClass() sử dụng Danh sách khởi tạo,

// Sử dụng Danh sách khởi tạo
class MyClass {
	Type variable;
public:
	MyClass(Type a):variable(a) { //Giả sử rằng Type là một lớp đã
                                      // đã được khai báo với các hàm
                                      // và toán tử thích hợp.
	}
};

Với Danh sách khởi tạo, trình biên dịch thực hiện theo các bước sau:

  1. Hàm tạo có tham số của lớp “Type” được gọi để khởi tạo: variable(a). Các đối số trong danh sách khởi tạo được sử dụng để sao chép trực tiếp cho variable.
  2. Hàm huỷ của Type được gọi cho đối tượng a vì nó đã ra khỏi hàm MyClass.

Như chúng ta có thể thấy từ ví dụ này nếu chúng ta sử dụng phép gán bên trong thân hàm tạo, có ba lệnh gọi hàm: hàm tạo + hàm hủy + một lệnh gọi toán tử gán bổ sung. Và nếu chúng ta sử dụng Danh sách khởi tạo thì chỉ có hai lời gọi hàm: sao chép hàm tạo + lệnh gọi hàm hủy. Hình phạt việc gán này sẽ nhiều hơn trong các ứng dụng “thực”, nơi sẽ có nhiều biến số như vậy.
Vui lòng viết bình luận nếu bạn thấy bất kỳ điều gì không chính xác, hoặc bạn muốn chia sẻ thêm thông tin về chủ đề đã thảo luận ở trên.


Đã đăng vào

trong

bởi

Bình luận

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *