变量与基本类型

学习目标:

变量与基本类型


学习内容:

一、基本数据类型

  • 算数类型
    char 字符 8位 (-128,127)
    short 短整型 16位 -32768~32767
    int 整型 32位
    long long 长整型 64位
    float 单精度浮点数 6位
    double 双精度浮点数 10位
    无符号类型 unsigned,程序里尽量不要用
    字面值常量literal,整型:十进制20, 八进制(0开头)024, 十六进制(0x开头)0x14;字符:‘a’, ‘hello world’
    转义序列:换行\n,单引用’,双引用’’

二、变量

  • c++中变量其实就是等于对象。但是一般来说,我们把内置类型相关的称为变量,与类相关的称为对象。

  • 初始化是在创建对象的时候给对象赋一个初值,而赋值是把对象当前的值擦除,赋一个新值。

  • 建议在创建对象的时候都进行初始化;

  • 四种常见变量初始化的方式:

    int a = 1;
    int b = {1};  // 列表初始化
    int c(1);
    int d{1};
    

    但是需要注意的是,列表初始化的初始值如果存在信息丢失(类型转换long double->int)的风险,编译器会报错;

  • 声明是让名字被程序所知,一般写在头文件中,声明规定了变量的类型和名字;而定义是负责具体的实现,还要申请存储空间,还可以给变量赋初始值。
    声明:extern int i;
    声明并定义i:int i;

  • 标识符
    要用具体的含义,不能定义a、b等;
    变量名一般用小写字母,如index;
    类名一半以大写字母开头,如Sales_item;
    变量名是多个单词组成,要区分开:student_loan或studentLoan;

三、复合类型

1. 引用

  • 形式:类型名 &别名 = 对象
    引用的类型必须和所引用的对象类型一致,除了常量引用;
    引用必须要初始化,即右边必须要有一个对象; 代码理解

    #include <iostream>
    int main(){
        int &a;        // 报错
    }
    
  • 右边必须是一个已经存在的对象,不能是一个常量;如果想引用常量,必须在前面加const,此时编译器会先生成一个temp对象,再进行引用; 代码理解:

    #include <iostream>
    int main(){
        int &a = 1;        // 报错
        const int &b = 1;  // 不报错
        // 编译器做的事
        // const int temp = 1;
        // const int &b = temp;
    }
    
  • 这里提到的引用其实是左值引用;

  • 引用即别名,引用只是为一个已经存在的对象取了另外一个名字;
    代码理解1:

    #include <iostream>
    int main(){
        int a = 1;
        int &b = a;
        for(int i = 0; i < 10; i++){
            a = i;
            // 输出 0-9
            std::cout << b << std::endl;
        }
    }
    

    代码理解2:

    #include <iostream>
    int main(){
        int a = 1;
        int &b = a;
        // 读取a和b的内存地址
        std::cout << &a << std::endl;   // 0x61fe14
        std::cout << &b << std::endl;   // 0x61fe14
    }
    
  • 引用后不管改动哪个值,两者都会更改; 代码理解:

    #include <iostream>
    int main(){
        int a;
        int &b = a;
        a = 10;
        b = 20;
        // 20 20
        std::cout << a << b << std::endl;
    }
    

2. 指针
指向地址的变量称为指针变量。 定义:int *a; 使用:a = &b; 也可以合起来写:int *a = &b;

  • int *a = &b;这句话的含义:定义了一个int型的指针变量a,它指向的是int变量b的地址。

  • 指针和引用的区别:指针本身就是一个变量,而引用只是为一个已存在的变量取了个别名;指针可以不初始化,和其他变量类型一样,会拥有一个不确定的值;引用无法重新绑定,指针可以随意赋值; &取地址符获取的是a的起始地址;

    #include <iostream>
    int main(){
        int *a;
        std::cout << &a << std::endl;  // 0x61fe18
    }
    
  • 指针定义的时候 * 说明p是一个指针变量;使用时 * 表示取p保存的地址对应的值; 获取对象的地址用取地址符&,利用解引用符*来访问对象

    #include <iostream>
    int main(){
        int a = 1;
        int *p = &a;
        // 输出a的(起始)地址  p里面保存的就是a的地址
        std::cout << &a << " " << p << std::endl;   // 0x61fe14  0x61fe14
        // 输出p的起始地址
        std::cout << &p << std::endl;  // 0x61fe10
        // 输出p保存的a的地址对应的值 = a的值
        std::cout << *p << std::endl;  // 1
    }
    
  • 【多重指针】判断指针变量自身的类型和指针变量指向的类型:在定义时,把变量名忽略,剩下的是什么类型这个指针变量就是什么类型;把变量名和前面的一个指针忽略,剩下的是什么类型这个指针指向的就是什么类型;

    #include <iostream>
    int main(){
        // 指针变量a自身的类型是int *
        // 指针变量a指向的类型是int
        int *a;
        int a = 1;
        int *b = &a; // b指向一个int类型的数a
        int **c = &b; // c指向一个int*型的指针b
        int **c = &a; // 错
        std::cout << a << *b << **c << std::endl;  // 三种方式输出a的值
    }
    
  • 使用指针时类型应该匹配;

    #include <iostream>
    int main(){
        int a = 1;
        double *b = &a; // 错
        double c = 1.1;
        int *d = &c;    // 错
    }
    
  • 可以通过指向的对象来改变指针变量所指向地址的值;也可以通过指针变量所指向地址的值来改变指向的对象值;

    #include <iostream>
    int main(){
        int a = 1;
        int *b = &a;
        a = 2;
        std::cout << *b << std::endl;  // 2
        *b = 3;
        std::cout << a << std::endl;  // 3
    }
    
  • 注意指针和引用混合使用,容易混淆;

    #include <iostream>
    int main(){
        int a = 1;
        int *b = &a;
        int &c = b;   // 错 b表示的地址  c表示int类型的一个别名  类型不符合
        int &c = *b;  // 对 *b表示a的值 c表示*b的一个别名
        std::cout << c << std::endl;
    }
    
  • 指针最好都初始化;指针初始化最好用nullptr,不要用NULL,如 int *p = nullptr;、

  • 指针比较:两个指针存放的地址相同(全为空 或 指向同一个对象 或指向同一个对象的下一个地址);

  • if(p)任何非零的指针都返回True;if(*p)任何值是非0的返回True;

    #include <iostream>
    int main(){
        int i = 0;
        int *p1 = &i;
        int *p2 = 0;
        if (p1){   // True
            std::cout << "p1" << std::endl;
        }
        if (p2){   // False
            std::cout << "p2" << std::endl;
        }
    }
    
    #include <iostream>
    int main(){
        int i = 1;
        int *p1 = &i;
        if (*p1){    // 任何值是非0返回True
            std::cout << "p1" << std::endl;
        }
    }
    
  • void *可以存放任意对象的地址,不管对象是什么类型;

    #include <iostream>
    int main(){
        int i = 1;
        void *p1 = &i;   // 合法
        float *p2 = &i;  // 不合法
    }
    
  • 指向指针的引用:引用本身不是对象,因此不存在指向引用的指针,但是存在对指针的引用;

    #include <iostream>
    int main(){
        int a = 1;
        int *b = &a;  // b指向一个int型的数
        // 【从右往左看】离变量名c最近的是& 说明c是一个引用,int *说明c引用的对象是一个int型的指针
        int *&c = b;  // c引用一个int型的指针  
    }
    
  • 定义多个指针变量

    #include <iostream>
    int main(){
        // a int型指针
        // b int型变量
        // c 引用int型变量b
        // d 引用int型指针a 
        int *a, b, &c = b, *&d = a;
    }
    

四、const限定符
const 类型名 变量名 = 初始值

  • const一旦初始化,就无法改变; const定义时一定要初始化;

    #include <iostream>
    int main(){
        const int a = 1;
        a = 2;  // 错
        const int c;  // 错
    }
    
  • const变量可以和其他变量一样参与赋值、运算等操作,但是其他需要改变const变量的操作都会报错;

    // 赋值时 不会改变const变量 不会报错
    const int a = 1;
    int b = a;
    
  • 默认情况下,const变量只在当前文件中有效,如果在多个文件中定义同一个const变量,等于分别在不同文件中定义了不同的独立const变量;

  • 如果想在不同文件之间共享const变量,需要在const变量定义之前添加extern关键字;

    extern const int bufSize = fcn();   // bufSize常量可以被其他文件访问
    

1. const和引用

  • 常量引用 最标准的常量引用

    #include <iostream>
    int main(){
        const int a = 1;
        const int &b = a; 
    }
    

    引用的类型必须和所引用的对象类型一致,但是有一个例外:常量引用。
    i. 常量引用右边可以为任意的,非常量/字面值/一个表达式/不同类型的引用;
    ii. 但是非常量是无法引用常量的;

    #include <iostream>
    int main(){
        // 常量引用非常量
        // 此时编译器会自动匹配
        // const int temp = 1;
        // const int &b = temp;
        int a = 1;
        const int &b = a;
    
        // 常量引用右边可以是任何东西
        const int &c = 42;  // 初始化为42
        const int &d = b * 2;  // 初始化为表达式
        
        // 不同类型的引用
        // 编译器
        // const int temp = e;
        // const int &f = temp;
        double e = 3.14;  // 3.14
        const int &f = e; // 3
        int &f = e;  // 错 没有意义  f其实是temp的别名 改变f其实是没有意义的  temp是不存在的
        
        // c是b的别名 b是const不可改的 但是c可以改=b也可以修改  错误
        int &c = b;  // 错  非常量无法引用常量
    }
    

    注意的点:常量引用非常量时,不能通过改变常量的值改变非常量。

    #include <iostream>
    int main(){
        int a = 1;
        int &b = a;  // b绑定了a
        const int &c = a;  // c也绑定了a  但是不允许通过c改变a 
    
        a = 2;   // 可以改变a
        std::cout << a << b << c << std::endl; // 2 2 2
        b = 3;   // 可以改变b
        std::cout << a << b << c << std::endl; // 3 3 3
        c = 4;   // c无法改变
    }
    

2. const和指针

  • 一般来说,指针的类型必须与其所指对象的类型一致,但是允许一个指向常量的指针指向一个非常量对象。因为常量指针说明指针指向的值不变,但是指针指向的地址是可以变的。

    #include <iostream>
    int main(){
        const double a = 3.14;
        double *ptr = &a;  // 错误  类型要一致
        const double *p = &a;
        double b = 3.14;
        p = &b;  // 常量指针p重新指向一个非常量对象b  (这句话不是赋值操作)
    }
    
  • 常量指针:const int *p 指针所指的对象是一个常量 所以指针所指的对象的值(*p)不可以改变 -> 底层const const修饰 *p,所以 *p不能修改 指针所指的地址(p)可以修改,但是指针指向的值(*p)不可以修改

    #include <iostream>
    int main(){
        int a = 1;
        int b = 2;
        const int *p = &a;  
        p = &b;     // 指针所指的地址可以修改
        *p = 2;     // 指针指向的值不能修改
        int const *p = &a;
        p = &b;     // 指针所指的地址可以修改
        *p = 2;     // 指针指向的值不能修改
    }
    
  • 指针常量:int *const p 指针本身是一个常量,所以指针本身的值是不可以修改的 -> 顶层const const修饰p, 所以p不能修改 指针指向的值(*p)可以修改,指针本身的值/所指的地址(p)不可以修改

    #include <iostream>
    int main(){
        int a = 1;
        int b = 2;
        int *const p = &a;
        *p = 2;        // 指针指向的值可以修改
        p = &b;        // 指针所指的地址不能修改
    }
    
  • 叠加使用:const int *const p 指针指向的值(*p)不能修改,指针所指的地址(p)也不能修改

    #include <iostream>
    int main(){
        int a = 1;
        int b = 2;
        const int *const p = &a;
        *p = 2;  // 指针指向的值不可以修改
        p = &b;  // 指针所指的地址不能修改
    }
    
  • 顶层const和底层const
    顶层const:指针本身是一个常量;int *const p 更一般的任意的对象是常量也可称为顶层const
    底层const:指针所指的对象是一个常量;const int *p 引用的const都是底层const:
    const int &ra = 10; 对顶层const取&,就变成底层const

    int i = 0
    int *const p1 = &i;  // 顶层const
    const int ci = 42;   // 顶层const
    // ci是顶层const 但是对一个顶层const取地址&相当于又加了一个底层const
    const int *p2 = &ci; // 底层const  
    const int *const p3 = p2;  // 前面底层const  后面顶层const
    // 常量引用在左侧,右边可以是任何东西
    const int &r = ci;  // 引用都是底层const
    
    const int &a = 1;  // 底层const
    int &b = a;  // 错 
    

    判断原则:当指向对象的拷贝操作时,顶层const不受什么影响(可以忽略不看),底层const必须一致才能成功赋值

    i = ci;  // 正确:ci是顶层const 直接忽略 两者都是int类型
    p2 = p3; // 正确:第二个const是顶层const 直接忽略 两者都是const int*类型
    
    int *p = p3; //错误  p3=const int*  p=int*  
    
    p2 = &i  // 正确 常量指针重新指向一个非常量对象
    

    引用不是对象,不能使用上面的判断条件。
    i. 常量引用如果在左侧,右侧可以接任何东西(特殊的常量可以引用非常量)
    ii. 非常量引用=常量(非常量引用常量),报错
    iii. 非常量 = 常量引用,对(赋值操作)

    const int &r = ci;  // 对
    const int &r = i;  // 对
    int &r = ci;  // 错  因为肯定不能通过r来修改const ci
    
    const int a = 10
    const int &d = a;  
    int f = d;  // 赋值操作  不改变常量的值  所以没关系
    

3. 常量表达式和constexpr变量

  • 常量表达式:值不会改变,且在编译过程就能得到计算结构的表达式。即类型必须是const,值是字面值常量。

      const int a = 20;  // a是常量表达式
      const int b = a + 1;  // b是常量表达式
      int c = 27;  // c不是常量表达式  c可变  
      const int sz = get_size()  // sz不是 运算时才知道sz的值
    
  • constexpr变量: c++11允许将变量声明为constexpr类型,以便由编译器来验证变量值是否是一个常量表达式。让该变量获得在编译阶段即可计算出结果的能力。 条件:声明为constexpr的变量必须是一个常量,而且必须用常量表达式初始化;

    constexpr int a = 20;  // 是
    constexpr int b = a + 1; // 是
    constexpr int c = size();  // 只有当size()函数是一个constexpr函数时才是
    
    int dim = 3;   // 错  dim不是常量表达式
    const int dim = 3;  // 对 dim是常量表达式
    constexpr int dim = 3;  // 对 修饰普通变量 让普通变量->常量表达式
    int a[dim] = {1, 2, 3};
    
  • constexpr仅对指针有效,对指针所指的对象无关;

五、处理类型
1. typedef类型别名

  • 类型别名:给类型取一个简单、易懂的别名

  • 传统方法:typedef

    typedef double de, *p;   // de = double, p = double*
    
  • c++11方法:using

      using S = Scale_item;  // 别名声明
      S s;  // 用别名声明一个对象
    
  • 类型别名和类型名是等价的:double a = 3.14; 等价于 de a = 3.14;

  • 复合类型的别名(后续补充)

2.auto类型说名符

  • c++11新标准引入auto类型说明符,可以让编译器替我们自动分析表达式的类型。

    auto i = 0, *p = &i;
    auto a = 0, b = 3.14;  // 错
    
  • 复合类型、常量和auto(后续补充)

3.decltype类型指示符

  • c++11新标准引入decltype,作用:选择并返回操作数的数据类型,而不关系它的值。

    decltype(f()) sum = x; // sum的类型就是函数f的返回类型
    
  • decltype、复合类型和const(后续补充)

六、自定义数据结构

  • struct: 定义类,strcut Student{}; 分号不能忘记
  • 尽量不要把类的定义和变量定义放在一起,分开写最好了;
  • 类内部名字必须唯一,但可以和类外名字重复;
  • 每个类对象都会有一份类成员的拷贝,修改一个对象的类成员,不会影响其他对象的类成员;
  • c++11可以为类数据成员提供一个类内初始值;
  • 头文件
    头文件中一般存放那些只能被定义一次的实体:类、const和constexpr变量等;
    预处理器: 确保头文件多次包含仍然能安全工作
    i. #include:在指定位置用头文件中的内容代替;
    ii. #define: 把变量名定义为预处理变量;
    iii. #indef: 检查预处理变量是否已经被定义,如果已定义执行后续操作,直到#endef为止;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值