c:c++定义、声明、初始化、赋值的区别、成员初始化列表、构造函数

声明:告诉编译器该变量存在,不分配内存空间,可以多次声明

/*extern关键字声明,不分配存储空间,一般位于多文件中工程中,声明在.h文件*/
extern int a;

多文件写extern关键字的用法:

test.h

#ifndef _TEST_H
#define _TEST_H
extern int a; //声明,尽量不要在头文件中定义初始化,会造成重复定义或者多重定义
#endif

test.c

#include "test.h"
int a = 1; //定义+初始化

demo.c

#include<stdio.h>
#include "test.h"

int main(){
    printf("%d\n",a);
    return 0;
}

运行结果为:

./demo 
1

定义:只能定义一次,创建了一个类型对象,为类型对象分配内存空间。

/*定义  分配存储空间   在单个文件中没有声明和定义的区分,声明就是定义,统称为定义,多文件中将声明和定义区别*/
int a;

初始化:在定义变量以后,系统为变量分配的空间内存储的值是不确定的,所以需要对这个空间进行初始化,以确保程序的安全性和确定性,给变量赋默认值。

/*声明+定义+初始化,通常直接叫做初始化。分配存储空间*/
int a=1;

赋值:变量在声明、定义或初始化完后对变量的值进行修改,即修改存储空间内的数据。

/*修改已经定义好的变量a的值为1。不分配存储空间。*/
a=1;

变量声明和定义的区别

声明:含有extern关键字且变量没有分配存储空间。变量可以声明多次,声明通常放在头文件(.h)中

定义:不含extern关键字或者含有extern关键字但是变量被初始化,分配存储空间。变量只能定义一次,定义放在源文件(.cpp)中,防止文件被多次引用,重复定义。

在类或者结构体中变量的声明、定义、初始化和赋值:

在类中定义和初始化统称为初始化(分配空间);

类或者结构体只是一个模板(Template),编译后不占用内存空间,书写好类模版实际上只是声明该类(包含类成员变量和函数的声明),不占用存储空间。只有在创建对象以后才会给成员变量分配内存,这个时候就可以赋值了。

类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存。但是,在定义类模版的时候不能对成员变量分配空间初始化或者赋值,因为类只是一种数据类型或者说是一种模板,本身不占用内存空间,而变量的值则需要内存来存储。

为什么静态变量不能在定义类模版直接定义初始化 或者在构造函数初始化列表中初始化?

因为static变量属于类本身,不属于类的某个实例化对象,所有对象都可以访问,程序的运行过程中只有一个副本。创建类的实例化对象时会对成员变量进行初始化工作,如果在定义类模版时直接对静态变量初始化 或者在构造函数初始化列表中初始化,都会重复定义该静态成员变量,相当于每一个实例化对象都有属于自己的一个静态成员变量,这显然是错误的,非静态成员才是每个实例化对象特有的。所以c++规定类的静态成员变量在类中声明,类外初始化(分配空间赋值)静态成员是单独存储的,并不是对象的组成部分

#include <iostream>
#include<vector>
using namespace std;

class A
{
public:
    static int n;
     A():n(1){
         n = 2;
     }

};
int main(){
        A a;
        cout<< a.n<<endl;
        return 0;
}
#include <iostream>
#include<vector>
using namespace std;

class A
{
public:
    static int n = 1;
     A(){
         n = 2;
     }
};
int main(){
        A a;
        cout<< a.n<<endl;
        return 0;
}

上述两种代码都是错误的,报错如下:

error: ‘int A::n’ is a static data member; it can only be initialized at its definition

下面调用方式正确:

#include <iostream>
#include<vector>
using namespace std;

class A
{
public:
     static int n; //声明
     A(){
         n =2;//构造函数第二段 计算赋值
     }
    
};
int A::n= 1; //初始化(分配存储空间)
int main(){
        A a;
        cout<< a.n<<endl;
        return 0;
}

C++类中const静态成员变量可以直接初始化

正常写法:

#include <iostream>
#include<vector>
using namespace std;

class A
{
public:
     static const int m;
     A(){
     }

};
const int A::m= 1;
int main(){
        A a;
        cout<< a.m<<endl;
        return 0;
}

直接类中初始化写法:

#include <iostream>
#include<vector>
using namespace std;

class A
{
public:
    static const int m = 2;
     A(){
     }
    
};
int main(){
        A a;
        cout<< a.m<<endl;
        return 0;
}

以下是类的声明、初始化、赋值

在C++中,实例化对象调用构造函数对非静态成员进行初始化,静态成员变量由程序员单独类外初始化, 静态const成员类外初始化或者直接定义类模版时初始化;

class circle
{
public:
  /*类中的成员变量仅仅是被声明并没有定义,告诉编译器存在该类型变量,并不分配存储空间*/
  /*类非静态成员变量在执行构造函数时被定义(分配存储空间)以及初始化、在构造函数内部被被赋值*/
	double pi; 
	float r;
  /*类静态成员变量在类内声明,类外定义同时初始化(分配存储空间)*/
	static string belong;
  /*类const静态成员变量直接初始化*/
  static const int m = 2;
  
  circle() //构造函数 ,当实例化对象时调用构造函数,对非静态成员变量进行初始化(分配空间),进入构造函数内部为赋值
	{
		r = 2; 
		pi = 3.14f;
		cout << "construct circlr" << endl;
	}
};

补充知识:

构造函数

1、构造函数是一种特殊的类成员函数,用来在对象实例化时初始化对象的成员变量,为对象成员变量赋初始值,完成对象的初始化工作
2、构造函数必须与类的名字相同,并且不能有返回值和返回值类型
3、每个类可以有多个构造函数。当开发人员没有提供构造函数时,编译器在把源代码编译成字节码的过程中会提供一个没有参数默认的构造函数,但该构造函数不会执行任何代码。如果开发人员提供了构造函数(如带参构造函数、拷贝构造函数),那么编译器就不会在创建默认的构造函数了。
4、构造函数总是伴随着new操作一起调用,且不能由程序的编写者直接调用,必须要由系统调用。构造函数在对象实例化时会被自动调用,且只运行一次;而普通的类成员函数方法是在程序执行到它时被调用,且可以被该对象调用多次。
5、构造函数不能被继承,因此它不会被覆盖,但是构造函数能被重载,可以使用不同的参数个数或参数类型来定义多个构造函数。

当实例化一个类对象时,需要调用构造函数进行对象初始化工作,构造顺序如下:

1.分配内存,调用构造函数时,隐式/显示的初始化各数据成员
初始化阶段可以是显式的或隐式的,取决于是否存在成员初始化表。隐式初始化阶段按照声明的顺序依次调用所有基类的缺省构造函数,然后是所有成员类对象的缺省构造函数。显示调用则使用初始化成员列表,调用指定的构造函数(如有参构造、或者拷贝构造)
2.进入构造函数后在构造函数中执行一般计算(赋值操作)
计算阶段由构造函数体内的所有语句构成。在计算阶段中,数据成员的设置被认为是赋值,而不是初始化

成员初始化列表:

初始化列表是类中非静态成员变量初始化的一种方式。初始化列表是类中构造函数的一部分

成员初始化列表的语法:

1、不能用于除构造函数之外的其他成员函数。
2、位于形参表的右括号之后,函数体左括号之后。
3、成员初始经列表由逗号分隔的初始化列表组成(前面带冒号)。
4、每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
5、类中包含以下成员,必须放在初始化列表位置进行初始化:
  一、非静态const数据成员。
  二、引用数据成员
  三、如果一个类成员,他本身是一个类或者结构,而且这个类成员没有默认的无参构造函数,只有带参数的构造函数,这个时候对类成员初始化时,必须调用类成员带参数的构造函数。如果再初始化列表中没有完成类成员初始化,代码会报错。
  四、子类初始化父类的私有成员
6、类的数组成员不能使用成员初始化列表进行初始化,而只能通过在构造函数体中对数组的各个成员进行赋值

成员初始化列表的函数原型

/*
类名::构造函数名(形参表)
:数据成员1(数据1),数据成员2(数据2)......数据成员n(数据n)
{
    函数体
}
*/

初始化列表使用方式:

class UnionFind{
private:
    vector<int> parent;
    int num;
public:
  /*默认构造函数*/
	UnionFind() : num(10), parent(10,0){//编译器按照声明时的顺序初始化,书写代码时也尽量按照声明顺序书写初始化列表
	}
  /*带参构造函数*/
  UnionFind(int n) : num(n), parent(n){
        for (int i=0;i<n;i++){
            parent[i]=i;
        }
    }
}

对比不使用初始化列表的方式:没写成员变量的初始化列表也会初始化,只不过初始化为随机值

class UnionFind{
private:
    vector<int> parent;
    int num;
public:
  /*默认构造函数*/
	UnionFind(){
		num=10;
		parent.resize(10,0);
	}
  /*带参构造函数*/
  UnionFind(int n){
        num = n;
        parent.resize(n);
        for (int i=0;i<n;i++){
            parent[i]=i;
        }
    }
}

列表初始化与构造函数初始化区别

当采用列表初始化的时候,代码直接调用了成员变量的构造函数,只需要一步就可以完成。
而在构造函数中初始化的时候,先调用的是成员变量的默认构造函数构造了类成员,然后再调用赋值运算符对之前默认构造的类成员进行赋值,这实际上进行了两步操作。
当类的结构较为复杂的时候,会存在性能上的差异。

以下几种情况必须使用初始化列表:

非静态成员变量在类中只是被声明,没有被定义,所以可以先不初始化,当我们实例化一个对象之后,必须进行初始化

1、 非静态const数据成员

const修饰的成员变量不能够被修改,所以需要在定义时候进行初始化(构造函数进入函数体之前),构造函数内部不会初始化,只赋值。

#include<iostream>
using namespace std;
class A {
public:
	A(int val):_val(val){}
	const int _val;
};
int main() {
	A a(2);
	cout << a._val;
	return 0;
}

2、类中引用类型的成员变量, 引用类型定义时候必须进行初始化

#include<iostream>
using namespace std;
class A {
public:
	A(int val):_mval(_val){}
	const int& _mval;
};
int main() {
	A a(2);
	cout << a._mval;
	return 0;
}

3、调用类中自定义类型成员的构造函数

如果一个类成员,他本身是一个类或者结构,而且这个类成员没有默认的无参构造函数,只有带参数的构造函数,这个时候对类成员初始化时,必须调用类成员带参数的构造函数。包含继承关系中 子类初始化无默认构造函数的父类

class B {
public:
    int num;
    B(int n) {
            this->num = n;
        }
};

class A {
public:
    B b;
    A() : b(10) {
    
    }
};
int main(int argc, char const *argv[])
{
    A a;
    return 0;
}

4、子类初始化父类的私有成员‘

必须使用成员初始化列表两个原因

​ 一、子类无法访问父类的私有成员、但要对父类的私有成员进程初始化

​ 二、继承中如果子类构造函数存在初始化列表,则运行父类的有参构造函数,如果不存在初始化列表,调用默认的构造函数,

但是如果在子类构造函数中调用父类的构造函数,则会重新生成一个父类对象,与初始化时产生的父类对象不同,因此必须使用成员初始化列表

#include <iostream>
 
using namespace std;
 
class a{
private :
	int i;
public:
	a() {}
	a(int x) :i(x) {}//初始化列表对private成员初始化
	void printa(){
		cout << "i = " << i << endl;
	}
};
 
class b :public a{//继承父类的public成员
private:
	int j;
public:
	b(int x, int y) :a(x)//使用初始化列表的形式初始化父类a中的private成员变量i
	{
        //i = x; i是private成员,直接赋值是不符合语法
        //a(x); 在子类中的构造函数中调用父类的构造函数也是错误的,相当于重新创建了一个新的对象,这个对象与刚开始初始化的父类对象是不同的两个父类对象
		j = y;
	}
	void printb()
	{
		cout << "j = " << j << endl;
	}
};
 
int main(int argc, char *argv[])
{
	b b1(34, 56);
	b1.printa();
	b1.printb();
	return 0;
}
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值