c++ primer 笔记

开始

编写一个简单的c++程序

每一个c++程序都包含一个或者多个函数,其中一个必须命名为main操作系统通过调用main来运行c++程序。下面是一个非常简单的main函数,他什么也不干就返回0

int main()
{
    return 0
}

一个函数分4个部分返回类型,函数名,形参列表(可以为0但是c语言不行),以及函数体
main函数返回类型必须为整形,因为int型式一种内置类型
函数定义的最后一部分是函数体,以左花括号开始右花括号结束,里面的句子叫做语句块,
在大多数的系统中main函数的返回值来表示返回的状态(return 0),0一般表示程序运行正常,但是非0右不同的含义,具体由操作系统定义,通常是错误类型(和真,假 bool类型分开)

输入输出

c++并没有定义任何输入输出(io)语句,取而代之的是包含了一个全面的标准库来提供io机制,c++里面的iostream库(c语言里面是stdio.h头文件)这个库包含了2个类型一个是istream和ostream,分别表示输入和输出,
这个标准库一个定义了4种io对象

  • cin(发音为see-in)的 istream类型对象也就是标准输入。
  • cout(发音为see-out)的ostream类型的对象也就是标准输出。
  • cerr(发音为see-err)的ostream类型的对象,用来输出警告和错误信息,也为标准错误消息
  • clog(发音为see-log)的ostream类型的对象,用来输出程序运行时的一般信息

使用io库

#include <iostream>
/*iostream.cpp*/
int main()
{
        std::cout << "Enter two number:" << std::endl; //enl=endline
        int v1=0,v2 = 0;
        std::cin >> v1 >> v2;
        std::cout << "The sum of" << v1 << "and" << v2 << "is" << v1 + v2 << std::endl;
        return 0;
}

  • 特别注意endl时endline的意思不是end加上数字1

解析

  • 首先程序的第一行#include 告诉编译器我们要使用iostream库,尖括号中的名字指出了一个头文件。每个使用标准库设施的程序都必须包含相关的头文件

  • main函数的函数体的第一句就执行了一个表达式。在c++中,一个表达式产生一个计算结果。它由一个或者多个运算对象和运算符组成。这条语句中的表达式使用了输出运算符<<在标准输出上打印信息 std::cout <<“Enter two numbers:” << std::endl; <<运算对象接受2个运算对象;左侧的运算对象必须是一个ostream对象,右侧运算对象时要打印的值。此运算符将给定的值写到给定的ostream对象中。而我们输出运算符的计算结果是其左侧运算对象,既计算的结果就是已经被右值赋予过的左值std::cout
    我们的输出语句使用了2次<<运算符。因此运算符返回其左侧的运算对象,因此第一个运算符的结果成为了第二个运算符的左侧运算对象
    它等于 (std::cout << “Enter two numbers:”) << std::endl;
    链中的每一个运算符的左侧都相同所以我们也可以写成这样
    std::cout << " Enter two numbers: ";
    std::cout << std::endl;
    第2个运算符打印endl这是一个被称为 操纵符的特殊值。写入endl的效果就是结束当前行并且与设备关联的缓冲区中的内容刷到设备中。缓冲区的刷新操作可以保证到目前为止程序所产生的所有输出都写入输出流中而不是在内存中等待写入流

  • 我们前缀std:: 是什么意思了?他的意思是指出名字为cout和cin定义在名字为std名称空间中,名称空间可以帮助我们避免不经意的名字定义冲突,以及使用库中相同名字导致的冲突。标准库定义的所有名字都在命名空间std中
    通过命名空间使用标准库有一个副作用,当使用标准库中的一个名字时,必须显式说明我们想使用来自命名空间的名字,例如std::cout 用作用域运算符::来指向我们想使用定义在命名空间std中的名字cout

  • 从流读取数据
    std::cin >> v1 >>v2 根据输入运算符>>和输出运算符类似他接受一个iostream作为其左侧运算对象,接受一个对象作为右侧运算对象,它会从io流中读取并且写入右侧的对象,然后返回其最测运算对象为计算结果因此他等价于
    (std::cin >>v1) >> v2;
    或者也可以这样写
    std::cin >> v1;
    std::cin >> v2;

控制流

while

首先while语句是一个循环语句

while (condition)   
{
        statement   
}  
//和c语言一样

首先检测condition是否为真如果为真就执行statement如果为假就跳过,然后再检测condition是否为真,为真继续执行,为假就跳过,…依次类推(和c语言里面的while一样)

for

for语句和while语句一样是循环的作用,而且和c语言中的for用法大部分一样,但是注意一点 在c语言里直接在for循环中定义变量是违法的但是在c++中可以

for ( int c; c < 10 , c++) //在c语言中违法但是在c++中不违法  
{
    statement;
}
  • 扩展while(std:cin >> a) ;代表无限的从缓冲区读取写入到变量a,那么(std:cin >> a) 这个式子的结果是什么了,我们如果从缓冲区读取一个数字就返回真,如果读取到EOF代表假然后就退出循环。

初探类

首先什么是类,每一个类实际上就是定义了一个新的类型,有点像c语言中的结构,但是类具体后就是对象,对象不可能只有数据类型还有方法,因此类名就是类型名字我们定义一个sales_item类

使用类

我们怎么使用我们自己定义的类了?我们使用#include来使用,但是我们记住c++内置的类是用<>包围头文件,对于不属于标准库的头文件用"“包围(前提是我们有这个类的头文件,头文件一般以.h结尾)

#inclue <iostrean>
#include "salas_item.h"   //使用自己定义的类   
int main()
{
    salas_item item1.item2;//定义了2个sales_items类对象item1和item2
    return 0;
}

初始成员函数

成员函数其实就是方法,就是定义类的一部分函数例如我们使用isbn方法

#include<iostream> 
#include"sales_item.h"
int main()  
{
    sales_items item1,item2;//定义2个sales_items类的对象item1和item2    
    std::cin >> item1 >> item2;  
    if(item1.isbn() == item2.isbn())  //使用sales_items类型的内置方法isbn,然后相比较
    {
        std::cout << item1 + item2 << std::endl;
        return 0;
    }   
    else 
    {
        std:cerr << "data must refer to same isbn" << std::endl;
        return -1;
    }
}

我们通常用一个类的对象名义来调用成员函数。例如,上面相等的表达式左侧运算对象的第一部分
item1.isbn()
使用点运算符.来表示我们需要名为item1的对象的isbn成员,点运算符只能用于类类型的对象,其左侧必须是一个类的对象而右侧必须是该类型的一个成员名,当我们用点运算符访问一个成员函数的时候,通常我们是想调用该函数,我们使用调用运算符()来调用一个函数括号里面放置实参,也有可能为空

c++基础

变量和基本类型

c++定义了算术类型和空类型(void)在内的基本数据类型

算术基本类型

算术基本类型分为2类一个是整形和浮点型
算术类型的尺寸在不同的机器上大小不一,但是c++标准定义了分配尺寸最小的标准

bool        //布尔类型      //未定义   
char        //字符          //8位(一个字节)   
wchar_t     //宽字符        //16位(2字节)    
char16_t    //unicode字符   //16位(2字节)   
char32_t    //unicode字符   //32位(4字节)
short       //短整形        //16位(2字节)
int         //整形          //16位(2字节)
long        //长整形        //32位(2字节)  
long long   //长整形        //64位(4字节)   
float       //单精度浮点数  //小数点后6位有效数字
double      //双精度        //小数点后10位有效数字
long double //扩展精度浮点数//小数点后10位有效数字   

其中bool类型也是整数类型他和c语言一样0是假,非0为真
和c一样int,short,long和long都是带符号的如果我们要定义它不带符号要在前面加上关键字unsigned
unsigned char c = -1,c的值为255为什么?
因为-1是一个有符号整形的常量在计算机中2进制用补码存储(因为方便逻辑电路加减),而-1的补码是11111111就是1的源码(00000001)取反(11111110)加上1(11111111) 又因为unsigned是无符号的所以11111111看成10进制255
unsigned char c = 256; 假设char类型的占8位,所以256转换成补码超出范围,所以他是未定义的,此时程序可能继续工作,也有可能崩溃,也有可能生成垃圾数据

初始化

初始化和我们以前了解的不一样,初始化不是赋值,初始化的含义是创建变量是赋予一个初值,而赋值含义是把对象的当前值擦除,而以一个新的值代替。
如果我们定义变量的时候没有指定初始值,则变量被默认初始化,此时变量被赋予了默认值,默认值到底为什么由变量的类型决定

变量声明和定义的关系

c ++ 支持分离式的编译机制,该机制允许一个程序分成多个文件,每个文件可以被单独编译。
但是我们 遇到而一个问题如果c ++ 支持多文件分离式编译的话我们在一个文件中想要调用另外一个文件的变量怎么办了?这时c++将声明和定义分开

  • 声明:
    使得名字位程序所知,一个文件如果想要使用别处定义的名字则必须包含对那个名字的声明
  • 定义:
    负责创建于名字关联的实体
    变量声明规定了变量的名字和类型,这一点上和定义相同。但是除此之外,定义还分配了空间,也可能给它赋予一个初值

如果想声明一个变量而非定义它,就在变量名字前面添加extern而且不要显示的初始化变量 但是你如果在extern后面再赋值那么就违背了声明不分配空间并且赋予初值的情况,这时候就成定义了

extern int i; //声明i而非定义了i   
int j ;//声明并定义了j    
extern int pi = 3.1415927;//定义

标识符

c ++ 的标识符和c语言的差不多,都是由字母(区分大小写),下斜线,数字组成,和c语言一样下斜线和字母可以放到首位,但是数字不行。
还有 c++ 的标识符长度没有限制

名字的作用域

和c语言一样变量定义在main函数之外的是全局作用域在函数内部的是块作用域,如果我们在外部定义main函数外部定义了一个全局变量i,然后我们在main函数内部使用i变量(std::cout << i << endl;)i的值就是那个全部变量i的值,当我们再在main函数定义一个相同的变量i来覆盖那个全局变量(int i = 6) ,这时 i的值是我们刚刚定义的那个局部变量的值6,但是我们那个在main函数之外定义的全局变量变了吗?其实他没有变,因为这两个变量存储的区域不一样全局变量存储在静态区域,而局部变量存储在动态区域但是我们怎么用全局变量中的那个i了?这样(std::cout << ::i << endl;)他的原理很简单,他只用了一个作用域操作符::来覆盖默认的作用于规则,因为全局作用域本身没有名字,所以当作用域操作符左侧为空的时候,默认想全局作用域发出请求,获取作用域操作符右侧名字对应的变量.

复合类型

复合类型是指基于其他类型定义的类型,主要的几种有引用和指针

引用

应用就是为对象其另外一个名字,也可以说应用既别名

c++引用用的是&符号(&符号可不是计算地址的&)

int ival = 1024;   
int &refval = ival;   //引用  把int类型变量refval指向ival,注意引用类型的初始值时对象不能是常量,而且对象的类型和它必须相等    
int &refval2; //报错,引用必须被初始化,因为定义引用的时候先把引用和初始值绑定这没有初始值    

一般我们在初始化变量时,初始值会被拷贝到新建的对象中,然而定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝到引用。一旦初始化完成,引用将它的初始值对象一直绑定在一起。
当ival的值发生改变refval的值也会跟着改变,当refval的值发生改变ival的值也会发生改变。

指针

c++的指针基础部分和c语言差不多但是要注意
首先在c ++ 中打印指针要这样

int a = 5;   
int *prt = &a;   
std::cout << *prt << std::endl;  //打印prt指针变量中指针指向的值也就是a的值     



空指针

空指针顾名思义就是不指向任何对象,下列列出几个生成空指针的的方法

int *prt1 =0 //直接将prt1初始化成字面常量0    
int *prt2 = nullprt   //等价于int *prt2 = 0  这时c++ 11新加的一种方法
int *prt3 = NULL //等价于int *prt3 = 0但是前提你要#include cstdlib   

nullprt时一种特殊的字面值,它可以被转换成任意其他的指针类型
而NULL其实就是一个预处理其值为0 它定义在cstdlib中

其他的指针操作

当我们的指针用于条件语句中,如果指针为0我们的条件为false(包括使用nulptr特殊字面值,他也是false),任何非0指针的条件值为非0也就是true,

void* 指针

指针有两个属性:指向变量对象的地址和长度,但是指针只存储地址,长度则取决于指针的类型;编译器根据指针的类型从指针指向的地址向后寻址,指针类型不同则寻址范围也不同,比如:

  • int*从指定地址向后寻找2字节(c++)作为变量的存储单元
  • double*从指定地址向后寻找4字节作为变量的存储单元
    void *则为“无类型指针”,可以指向任何数据类型
double obj = 3.14,*ptr = &obj;   
void *ptr = &obj;   //void *指针可以指向任何数据类型所以这个obj不限于double    

void *指针能做的事情有限,我们可以拿他和别的指针比较,作为函数的输入和输出,或者赋给另外一个void *指针
我们不能直接操作void指针所指向的对象,因为我们不知道void * 这个对象指向的时什么数据类型,

const限定符

const在c++中和c语言中用处一样,注意const的意思为read-only
比如
const int bufsize = 512; //这样我们就把bufsize设置成一个常量,扔个输入更改bufsize值的行为都会引发错误,
因为一旦创建const对象后就不能被改变,所以const对象一定要初始化,
const int k; //错误,因为k是一个未经初始化的常量
const限定符只在一个文件内有效,如果一个程序有多个文件那么const就无效了,因为多个文件出现多个同名的const变量时候,其实等同于再不同的文件中定义了相同名字的独立变量,那么我们怎做了?我们再const前面加上extern即可。

const的引用

我们可以把引用绑定到const对象上,就像绑定到其他的对象一样,我们称之为对常量的引用

const int ci = 1024;   
const int &r1 = ci;    
r1 = 42//错误的r1时对常量的引用,也就是应用绑定在一个常量上    
int &r2 = ci;//错误,试图让一个非常量引用指向一个常量对象,如果r2引用可以改变那么ci也能改变,就违反了const int ci的意图

我们知道引用的类型必须要和引用对象的类型一致,但是有2个例外
第一种时再初始化常量引用的时候允许用任意表达式作为其初值,只要该表达式的结果可以转换成引用的类型即可。例如

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

其上述的代码编译器其实将他们改成了这样
const int temp = dval;
const int &ri = dval;
这样ri其实绑定了一个零时的量
当ri不是常量的时候我们能来想一下,如果ri不是常量我们就可以给ri赋值,这样就可能该ri对象的值,但是我们ri绑定的临时变量temp,而非dval,我们要改变的是dval的值不是temp的值,所以这样就没有必要了

上面更多代码我们都用到了常量引用,引用到了一个非常量对象上

int i = 10;   
int &ci1 = i;    
const int &ci2 = i ;//正确,c++中常量引用的确可以引用到非常量对象上   
i = 2;
ci1 = 2;//正确ci1不是常量所以我们可以赋值,这样i也变成了2,而且ci2也变成了2虽然他是常量引用    
ci2 = 2;//错误ci2是一个常量引用这个引用是read-only所以不能改变其对象,但是我们可以通过上述的2种方法改变

引用和指针还是不一样
const &cr1 = i;//代表cr1是read-only但是可以改i的指
const *cr2 = &i//代表i的值是read-only也是一个常量指针

指针和const
const double pi =3.14//pi是一个常量,他的值不能改变
double *ptr = &pi; //错误ptr只是一个普通的指针,而pi则是一个常量,如果可以那么意味着可以通过ptr改变pi的值   
const double *cptr = &pi;   //正确cptr可以指向pi    
*cptr = 42;//错误不能给*cptr赋值因为他是const的

指针类型必须与其所指定的对象类型一致,但是有2个例子例外。第一种例外是允许令一个指向常量的指针指向一个非常量对象;
double dval = 3.14;
cptr = &dval;
和常量引用一样,指向常量(也就是不能改变其 对象但是值我们可以通过取他的方法改变前提是没有const的值和常量引用一样但,比如const int *ptr = i;常量引用就是不能改变常量引用的对象,但是对象的值我们可以通过其他的方法改变)的指针也没有规定其所指向的对象必须是一个常量。所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。

简而言之
pointer to const:指针常量
const pointer:常量指针

  • const (即*在const之前):距离const最近的是那个指针ptr,也就是“ptr是只读的”,所以恰当的描述就应该是const pointer

const * p(即 * 在const之后):距离const最近的是*p,也就是“这个指针p的解引用的结果是只读的”,所以不能够通过指针修改这个指针的解引用,所以恰当的描述是pointer to const

顶层const

指针是一个复用类型,所以指针本身是一个常量和指针指向的对象是一个常量本身就是2个问题
所以我们用顶层(top-level const)和底层(low-level const )
顶层(top-level const):指针(对象)本身就是一个常量 (就是指针常量)
底层(low-level const ):指针所指向的对象(或者就是一个对象)是一个常量 (就是常量指针)
顶层和底层并不只限于2层而且类型也不限于指针,我举指针的例子是为了更好的理解,

int i = 0;   
int *const p1 = &i;//顶层const 也是一个指针常量     
const int ci = 42;//顶层const     
const int *p2 = &ci //底层const 也是一个常量指针     
const int *const p3 = p2 //靠右边的const是顶层const,靠左边的是底层const     
const int &r = ci;//用于引用的const都是底层const
常量表达式(constexpr)

常量表达式是指不会改变并且在编译过程中就能得到计算结果的表达式,所以说constexpr是在编译期指定了常量,但是const并没有说明在编译期还是在运行期指定常量,我们还要记住constexpr还有一个重要的作用他在编译期就能得到计算结果,这大大的提高了效率这也是constexpr存在的最大一部分意义,我们定义一个constexpr函数,在我们编译的时候他就得出了计算结果在运行的时候大大的提高了效率

显然字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。也就是对象是const标识符也是const才是常量表达式

const int max_filles = 20;//是常量表达式
const int limit = max_filles+1;//是常量表达式   
int staff_size = 27;//不是常量表达式     
const int sz = get_size();//不是常量表达式  

sz为什么不是常量表达式了,因为get_ size的值要等到程序运行后才得到 为什么staff_size不是常量表达式了?因为他的类型是int没有加上const为对象做限定,**所以我们讲判断一个语句是不是常量表达式首先他的初值要是变量而且他的对象也必须是常量(被const限制了,除非用后面c11新提出的constexpr类型直接指定)
在一个复杂的程序中很难分辨一个初始值到底是不是常量表达式,c++11新的标准规定,允许将变量声明未constexpr类型 以便由编译器来验证变量的值是否是一个常量表达式。

constexpr int mf = 20;  //20是常量表达式   
constexpr int limit = mf + 1 //mf+1是常量表达式    
constexpr int sz = size()  //只有当size是一个constexpr函数的时候这才是一条正确的语句    

注意constexpr的对象是一个指针的时候是什么情况,当constexpr的对象是一个指针的时候自动的把这个指针转换成顶层const,

constexpr int *q = nullptr

处理类型

对于程序越来越复杂,用到的类型也是越来越复杂,复杂在几个方面首先,类型的名字我们容易拼错,类型的作用我们也有可能记不住

类型别名

类型别名(type alias)是一个名字,他是某种类型的同义词,使用类型别名有很多的好处,他让复杂的类型别名变得简单明了,易于理解和记忆
有2种方法可用于定义类型别名最传统的方法就是typedef

typedef double wages;   //wages是double的同义词    
type wages base ,*p;   //base是double的同义词 p是double *的同义词

新的标准用别名声明 来定义类型的别名
using SI = salas_item;
然后我们就可以用SI来代替salas_item,用wages来代替double

typedef char *p;   
const p cstr = 0; //cstr是指向char的常量指针 顶层const   
const p *ps; // ps是一个指向char的指针   底层const

以上的p被替换成*char所以前面一个式子其实是const * char cstr = 0 cstr成为了一个顶层const而后面一个式子const p *ps ps是一个底层const和上面的式子有本质的区别

猜你喜欢

转载自blog.csdn.net/qq_37026934/article/details/82799921