c++基础第一次

先看一下c++的基本内置类型吧
和C语言有着一样的类型,从bool,char,short, int,long,float,double,long double还有不要忘了指针类型。其中的细节不再深究。

其中有宽字符:wchar_t
来源typedef short int wchar_t;所以 wchar_t 实际上的空间是和 short int 一样。

类型转化
当在程序的某处我们使用了一种类型而其实对象应该取另一种类型时,程序会自动进行类型转化。
无符号类型赋值超出它表示范围的值,结果是初始值对无符号类型表示数值总数取模后的余数。
当我们赋给带符号类型一个超过它表示范围的值时,结果是未定义的。
如果表达式中既有带符号类型又有无符号类型,则隐式转换,带符号数转化为无符号数。

字面值常量,每一个字面值常量都对应一种数据结构,字面值常量的形式和值决定了它的数据类型。

20  /*十进制*/        024/*八进制*/     0x14/*十六进制*/

字符串字面值的类型是由常量字符构成的数组,这里编译器在每个字符串的结尾添加一个空字符(’\0’),字符串字面值的实际长度比内容多1.
两个字符串字面值位置紧邻而且仅由空格,缩进和换行符分隔,这两个就是一个整体,比如一行写不开就可以换下一行继续写。
前面说到每个字符都有对应的数字,在转义序列如’\n’中,存在泛化的转义序列:\x后跟多个十六进制数字;\后跟1、2或3个八进制数字(最多三个,若超过三个八进制数字,只有前三个数字与\构成转义序列)

std::string book("0-201-78345");

库类型std::string,像iostream一样,string也是在命名空间std中定义的。
在c++中,初始化和赋值不是一样的操作:初始化不是赋值,初始化的含义是创建变量时赋予一个初始值,而赋值是将对象的当前值擦除,并以一个新值代替。

初始化多种多样

int a = 0;
int b = {0};
int c(0);
int d{0};

注意在c++11标准中, 用花括号{}初始化变量称为列表初始化。一个重要特点是:当使用内置类型的变量时,如果我们使用列表初始化而且初始化存在丢失信息的风险,编译器会报错。(如给int类型的变量列表初始化一个double类型的值)

默认初始化:1.定义于任何函数之外的变量被默认初始化为0(全局变量)
2.定义于函数体内部的内置类型变量不被初始化(一个未被初始化的内置类型的变量的值是未定义的)。
3.每个类自己决定初始化对象的方式,绝大多数类都支持无须显式初始化而定义对象。类的对象如果没有显式初始化,其值由类确定(string类规定如果没有指定初值就生成一个空串。

支持分离式编译,将声明和定义区分开,变量声明规定了变量的类型和名字,而定义不仅做了这些,还申请存储空间,可能为变量赋予一个初值。

如果只想声明一个变量而非定义它,在变量名前添加extern,而且不要显式初始化变量。

extern int i;   //声明一个int类型的变量i ,而非定义。

任何包含显式初始化的声明都是定义,加extern也不例外(抵消了extern的作用),extern语句如果包含初始值就变成定义了。

extern double d = 3.14;  //定义

在函数体内部如果试图初始化一个由extern关键字标记的变量,将引发错误。

变量只能被定义一次,但可以声明多次。

如果在多个文件中使用同一个变量,必须将声明和定义分离。变量的定义只能出现在一个文件中,其它用到该变量的文件中必须进行声明,不能重复定义。

c++标识符:由字母,下划线_,数字组成,数字不能开头。

作用域
主函数main的名字main定义于所有的花括号之外,有着全局作用域,在整个程序的范围之内都可以使用。
若在函数内部定义了和全局变量同名的新变量(最好不要这样做),局部变量会在函数体内“覆盖”全局变量,致使此后函数体内访问的是同名的局部变量(将全局变量的作用域改为局部变量的作用域)。
但可以使用显式的方式访问全局变量

std::cout << ::global << std::endl;    //显式访问全局变量global

使用作用域操作符::覆盖默认的作用域规则,因为全局作用域没有名字,当作用域操作符左侧为空时,向全局作用域发出请求来获取作用域操作符右侧名字对应的变量。

引用&
引用实际上就是给【对象】起了一个别名。引用必须初始化。

int i = 0;
int &b = i;   //b指向i,而非拷贝。

定义引用时,引用和初始值绑定在一块,无法令引用重新绑定到另一个对象,必须初始化。
引用本身并不是对象,不能定义引用的引用。一般引用的类型与其绑定的对象的类型一致,而且不能和字面值或者计算结果绑定在一块。(有特殊情况)
看似操作引用,实际上是操作与引用绑定的对象的值。

指针*
指针本身就是一个对象,允许进行赋值和拷贝。指针存放对象的地址(用取地址符&),在x86-64中指针是8个字节(32位系统中为4字节)
一般情况下,指针的类型都要和它指向的对象的类型严格匹配。指向其他类型就报错。不能访问无效指针,空指针或者没有含义的指针。
解引用(*)仅适合于确实指向了对象的有效的指针。

初始化空指针

int *p1 = nullptr;   //字面值nullptr等价于 *p1 = 0
int *p2 = 0;         //直接初始化为字面常量0
#include <cstdlib>
int *p3 = NULL;      //等价于*p3 = 0   , NULL是预处理变量,不属于std命名空间

当遇到一个预处理变量时,预处理器会将它自动替换为实际值。

指针和引用的区别:
引用本身不是一个对象,一旦定义一个引用,就无法再重新绑定到另一个对象,每次使用引用都是访问它最初绑定的那个对象。
指针本身就是一个对象,只要赋给一个新的有效地址,就可以指向一个新的对象。

void指针
可以存放任何类型对象的地址,但什么类型是未知的。
1.和别的指针比较
2.作为函数的输入和输出
3.赋给另一个void
指针

指向指针的指针(**)
即指针的地址放在另一个同一类型的指针中。

指向指针的引用:指针的别名
引用不是一个对象,不能定义指向引用的指针。指针是一个对象,可以定义对指针的引用。

const限定符
为了定义一个变量而不能改变它。

const int buffsize = 100; //任何试图赋值buffsize的操作都引发错误

因为const对象一旦创建后不能再改变,所以const对象必须初始化。

编译器会在编译过程中把所有用到buffsize的地方替换为100。
若多个函数使用到同一const对象,需要在不同文件中对它定义。为避免对同一变量的重复定义,默认情况下,const对象仅在文件内有效。

如果想在多个文件之间共享const对象,即只有一个文件中定义const,其他文件中声明并使用它。
方法:对于const变量不管是声明还是定义,都添加extern 关键字,只需定义一次,其他文件只需用extern声明即可。

const引用(对常量的引用)
对常量的引用不能用于修改它所绑定的对象。

const int i = 0;
const int &r1 = i;   //引用和绑定的对象都是常量
int &r2 = i;    //错误,不能用一个非常量引用一个常量对象

前面说到引用的类型必须与其所绑定的对象类型一致,第一种例外:(类型不一致)
在初始化常量引用时,允许用任意的表达式作为初始值(只要结果能够转化为正常类型),尤其是允许一个常量引用绑定非常量的对象、字面值,甚至是一般表达式。说明白点就是常量可以绑定非常量(可以不让你变),但非常量(可以改变)不能绑定常量。
那么一个常量引用绑定到另一种类型时发生了什么呢?

double dval = 3.14;
const int &r1 = dval;

编译器把上述过程变成:

const int temp = dval;
const int &r1 = temp;            //这里temp是一个临时量对象。

对const的引用可能引用一个并非const的对象,常量引用仅对引用可参与的操作做出了限制,对于引用的对象本身是不是一个常量不做限定。

int i = 0;
const int &r2 = i //r2也绑定对象i,但不允许通过r2修改i的值


指向常量的指针不能用于改变其所指向对象的值,想存放常量对象的地址,只能使用指向常量的指针。

const double pi = 3.14;
const double *ptr = &pi;

前面提到指针的类型必须和它所指向对象的类型一致,第一种例外:
允许一个指向常量的指针指向一个非常量的对象(意思是我可以指向,但通过我来更改可不行)

double dval = 3.14;
const double *ptr = &dval;      //不能通过ptr来更改dval的值

和常量引用一样,指向常量的指针也没有规定其所指向的对象必须是一个常量。
所谓指向常量的指针仅仅要求不能通过该指针来改变对象的值,但没有规定不能通过其他途径改变。

指针本身也是对象,允许把指针定为常量。常量指针必须初始化,而且一旦初始化完成,它的值(保存的地址)就不能再改变。
将*放在const之前------------>>>

int *const ptr = &val;      //ptr 将一直指向val

这里有两个概念:
顶层const(指针本身就是个常量)
底层const(指针指向的对象是一个常量)
一般的,顶层const可以表示任意的对象是常量
底层const与指针和引用等复合类型的基本类型部分有关。
一、指针类型既可以是顶层const,也可以是底层const。
二、用于声明引用的const都是底层const。
在拷贝操作上
顶层const没有区别
对底层const有限制,当执行对象的拷贝时,拷入和拷出的对象必须有相同的顶层const资格,或者两个对象的数据类型一致。(非常量可以转换为常量

常量表达式
值不会改变而且在编译过程就能得到计算结果的表达式。
例如:字面值、用常量表达式初始化的const对象

constexpr int i = 0;

c++11中允许变量声明为constexpr类型由编译器验证变量的值是否是常量表达式。

声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。如果你认定一个变量用一个常量表达式初始化,就声明为constexpr.

一个constexpr指针的初始值必须是nullptr或者0,或者存储在某个固定地址的对象。
1.函数体内部定义的变量一般没有固定的地址,不能用constexpr。
2.而定义于所有函数之外的对象(全局变量)地址固定不变,能用来初始化带constexpr的指针。
3.还有就是允许函数内部定义一类有效范围超过函数本身的变量,和全局变量一样。
constexpr把它所定义的对象置为顶层const

constexpr int *ptr = nullptr;

等价于

int *const ptr = nullptr;

处理类型别名
1.typedef关键字
2.使用别名声明:using SI = sales_item;

使用类型别名不能把原来的格式替换来进行理解:

typedef char *pstring;
const pstring cstr = 0;     ///cstr是指向char的常量指针

若换回去:

const char *cstr = 0;  

变成指向const char的指针,即指向一个常量。而非一个常量指针。


auto类型说明符
auto让编译器通过初始化来推算变量的类型,auto定义的变量必须初始化。

使用auto能在一条语句中声明多个同一类型的变量(因为一条语句只能有一个基本类型)

auto一般会忽略顶层const,底层const会被保留下来。
为了不忽略顶层const:

const auto f = ci; ///ci的推演类型是int,f是const int 

错误:不能为非常量引用绑定字面值

auto &h = 42;
const auto &j = 42;     //可以为常量引用绑定字面值

decltype 类型指示符
选择并返回操作数的类型。
1.如果decltype使用的表达式是一个变量,则decltype发那会该变量的类型。(包括顶层const和引用)
2.如果decltype使用的表达式不是一个变量,则返回表达式结果对应的类型。

int i = 0, *p = &i;
decltype(*p) c = 0; 

当表达式的内容是解引用操作时,decltype将得到引用类型。因为解引用指针可以得到指针所指向的对象,而且还能给这个对象赋值。
decltype(*p)的结果类型就是int&,不是int。
注意decltype对于变量名加括号的问题
1,如果decltype使用的是一个不加括号的变量,得到的结果就是该变量的类型。
2,如果给变量加上一层或者多层括号,编译器则当成一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,这样的decltype就会得到引用类型。

decltype((i)) d = j;          //d是int&类型

不加括号时,只有当变量本身是引用时,才得到引用类型。

自定义数据结构

c++允许以类的形式定义数据结构。

struct sales_data{
	std::string book;
	usigned units_sold = 0;
	double revenue = 0;
};

类以关键字struct开始,后面跟类名和类体。类体由花括号包围成一个新的作用域,类内部定义的名字必须唯一,但可以和类外部定义的名字重复(作用域不同)
类内的成员可以有类内初始值,没有初始值的成员会默认初始化。使用点操作符(.)访问类内成员

类一般不定义在函数体内,当在函数体外部定义类时,在各个指定的源文件中可能只有一处该类的定义,如果要在不同文件中使用同一个类,类的定义就必须保持一致。为此,类通常定义在头文件中,而且类所在头文件的名字与类的名字一样。

头文件

头文件通常包含那些只能被定义一次的实体,头文件中也可以包含其他头文件(如需要string就要包含string.h头文件)
这时可能有多个头文件都需要某一头文件,每个头文件中都有包含,确保头文件多次包含扔能安全工作的是预处理器,看到#include标记时就用指定的头文件的内容代替#include。
上面提到预处理变量,(巩固一下:在编译之前,预处理器负责将程序中的预处理变量替换为它的真实值)分为【已定义】和【未定义】
预处理指令
1.#define指令把一个名字设定为预处理变量
2.#ifdef当且仅当变量已定义时为真,#ifndef当且仅当变量未定义时为真
3.一旦检查结果为真,则执行指定操作直至遇到#endif指令为止。

在程序执行过程中,第一次包含头文件时,#ifndef为真,使用#define直到#endif,将预处理变量设为已定义,并将头文件中的内容拷贝。再一次遇到时,#ifndef判断为假,编译器忽略后续操作。

#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct sales_data{
	std::string book;
	usigned units_sold = 0;
	double revenue = 0;
};
#endif

整个程序中的预处理变量包括头文件保护符必须唯一,通常基于头文件中类的名字来构建保护符的名字,确保其唯一性。一般把预处理变量的名字全部大写

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值