c++ primer 笔记

c++ primer 学习笔记

变量

列表初始化

变量中有个列表初始化感觉挺重要的,列表初始化是c++11中的新特性,看书有点没看明白,看了两篇文章1文章2感觉还挺清晰的.

那么什么是列表初始化呢?其实就是一种新的初始化方式,适用于各种类型,可以直接在变量名后面加上初始化列表来进行对象的初始化。

vector<string> articles={"a","an","the"};
int a{111};
列表初始化的一些规则

首先说下聚合类型可以进行直接列表初始化,这里需要了解什么是聚合类型:

  1. 类型是一个普通数组,如int[5],char[],double[]等
  2. 类型是一个类,且满足以下条件:
    • 没有用户声明的构造函数
    • 没有用户提供的构造函数(允许显示预置或弃置的构造函数)
    • 没有私有或保护的非静态数据成员
    • 没有基类
    • 没有虚函数
    • 没有{}和=直接初始化的非静态数据成员
    • 没有默认成员初始化器
struct A {
    int a;
    int b;
    int c;
    A(int, int){}
};
int main() {
    A a{1, 2, 3};// error,A有自定义的构造函数,不能列表初始化
}

上述代码类A不是聚合类型,无法进行列表初始化,必须以自定义的构造函数来构造对象。

struct A {
    int a;
    int b;
    virtual void func() {} // 含有虚函数,不是聚合类
};

struct Base {};
struct B : public Base { // 有基类,不是聚合类
      int a;
    int b;
};

struct C {
    int a;
    int b = 10; // 有等号初始化,不是聚合类
};

struct D {
    int a;
    int b;
private:
    int c; // 含有私有的非静态数据成员,不是聚合类
};

struct E {
      int a;
    int b;
    E() : a(0), b(0) {} // 含有默认成员初始化器,不是聚合类
};

上面列举了一些不是聚合类的例子,对于一个聚合类型,使用列表初始化相当于对其中的每个元素分别赋值;对于非聚合类型,需要先自定义一个对应的构造函数,此时列表初始化将调用相应的构造函数。

std::initializer_list

我们平时开发使用STL过程中可能发现它的初始化列表可以是任意长度,大家有没有想过它是怎么实现的呢,答案是std::initializer_list,看下面这段示例代码:

struct CustomVec {
    std::vector<int> data;
    CustomVec(std::initializer_list<int> list) {
        for (auto iter = list.begin(); iter != list.end(); ++iter) {
            data.push_back(*iter);
        }
    }
};

我想通过上面这段代码大家可能已经知道STL是如何实现的任意长度初始化了吧,这个std::initializer_list其实也可以作为函数参数。

注意:std::initializer_list,它可以接收任意长度的初始化列表,但是里面必须是相同类型T,或者都可以转换为T。

const

constxexpr

c++新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,并且必须使用常量表达式初始化。

关于通过函数初始化constexpr变量的初始值,普通函数是不能的,可以使用constexpr函数去初始化constexpr变量。

类型转换

warning:切勿将带符号类型与无符号类型混用,这样会导致结果产生异常,如a-1,b=1,a*b=-1,当a,b都为有符号int型时,结果是如此,但是当a是无符号类型时,结果就会等于4294967295,所以切勿将有符号类型与无符号类型混用。

auto与decltype

两者均可将一个变量定义成一个表达式的类型,但是有一点不同点需要注意,如果希望推断出的auto类型是一个顶层const,需要明确指出:

const ci = 1;
const auto f = ci;

如果不加const的话,f变量就只是一个int型,并不是一个顶层const。

引用也同理,如果想将结果定义成引用类型的话,需要明确中指出

auto &g = ci;

如果不添加的话,ci是int &类型,定义出的g仍是int类型。

还有一点不同就是decltype的结果类型与表达式的形式密切相关。有一种情况需要特别注意:如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型,如果给表达式加上了一层或者多层括号,编译器就会把它当成是一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型。

//decltype的表达式如果是加上了括号的变量,结果将是引用
decltype((i)) d;  //错误,d是引用类型,定义时需要初始化
decltype(i) e;   //正确,e是一个未初始化的int变量

预处理器

预处理器概述:

确保头文件被多次调用还能安全正常工作的技术是预处理器,它由C++语言从C语言继承而来,预处理器是在编译之前执行的一段程序,可以部分的改变我们所写的程序,当预处理器看到#include标记时就会用指定头文件的内容代替#include。

C++程序还会使用的一项预处理功能是头文件保护符,头文件保护符依赖于预处理变量。预处理变量有两种状态:已定义和未定义。

#define指令把一个名字设为预处理变量,另外两个指令则分别检查某个指定的预处理变量是否已经被定义。

#ifdef当且仅当变量已被定义时为真,

#ifndef当且仅当变量未被定义时为真,

一但检查结果为真时,则执行后续操作直至遇到#endif指令为止。

使用这些功能可以有效的防止重复包含的发生。

#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<string>
struct Sales_data {
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};

#endif

第一次包含Sales_data.h时,#ifndef的检查结果是真,预处理器将顺序执行后面的操作直至#endif处为止,此时SALES_DATA_H的值变为已定义,而且Sakes_data.h也会拷贝到我们的程序中来。后面如果再一次包含Sales_data.h,则#inndef的检查的结果为假,编译器将会忽略#ifndef到#endif之间的部分。

但是在实际开发过程中,直接使用#pragma once就可以了,方便又简洁。

warning:预处理器变量无视c++中关于作用于的规则。

string

getline函数

getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也读进来了),然后把所读的内容存入到那个string类型中去(注意不存换行符)。和输入运算符一样,getline函数也会返回他的流参数,所以我们也能用getline的结果作为条件

int main()

{
	string line;
	while(getline(cin,line))
		cout<<line<<endl;
	return 0;
}
size()函数

size函数有一点需要注意的是它的返回值并不是int型,而是一个unsigned int型,也就是无符号整型,前面也提到过,有符号类型一定不能和无符号类型混用,所以要切记如果表达式中存在size()函数就一定不要再使用int型了。

不一样的for循环

c++11新标准中提供了一中语句:范围for(range for)语句。这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作。其语法形式是:

for(declaration : expression)
	statement

其中,expession部分是一个对象,用于表示一个序列。declaration部分负责定义一个变量,该变量将被用于访问序列中的基本元素。每次迭代,declaration部分的变量都会被初始化为expression部分的下一个元素值。

string str("woshinidie");
for(auto c : str)
    cout<<c<<endl;

//运行结果为 : 
	w
	o
    s
    h
    i
    n
    i
    d
    i
    e
string s("Hello World!!!");
decltype(s.size()) punct_cnt = 0;
for(auto c : s)
    if(ispuntc(c)) //判断是否为标点符号
    	++punt_cnt;
cout << punt_cnt << "个标点符号在" << s << "中" << endl;

//运行结果:3个标点符号在Hello World!!!中

现在已经可以通过范围for对其进行读,那么我们该如何对其进行改的操作呢?很简单,只要在定义declaration时将其定义为引用就可以了。

c_str()函数

c_str()函数可以返回一个c风格类型的字符串,有什么用呢?当程序不得不使用一个c风格字符串时,就有用了。

string s="我是你爹";
const char* str=s.c_str();

执行完c_str()函数后程序一直都能使用其返回的数组,但如果后续改变了s的值可能就会让之前返回的数组失效。

函数总结:

ispunct(char c)

判断字符c是否为标点符号。

toupper(char c)

将字符c转化为大写。

isspace(char c)

判断是否为空格。

vector

vector支持的操作
v.size()返回v中元素的个数
v.push_back(t)向v的尾端添加一个值为t的元素
v[n]返回v中第n个位置上的元素的引用
v1={a,b,c…}用列表中元素拷贝替换v1中的元素
<, <=, >, >=顾名思义,一字典顺序进行比较
v.empty()如果v不含有任何元素,返回真:否则返回假

迭代器

就像之前见到过的size函数返回值类型为unsigned int一样,迭代器的精确类型我们也不知道,而实际上,那些拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型:

vector<int>::iterator it;     //it能读写vector<int>中的元素            
string::iterator it2;         //it2能读写string对象中的元素

vector<int>::const_iterator it3;      //it3只能读元素,不能写元素
string::const_iterator it4;           //it4只能读字符,不能写字符

const_iterator和常量指针差不多,实际上我们在实际使用时可以直接使用auto交给编译器来决定变量类型,如下

auto it=v1.begin();

这样的写法有一点需要注意的是,即使v1.begin()是一个常量iterator类型,auto也能将it设置为const_iterator,而前面学的auto是无法自动设置const的,这一点我觉得可能因为const_iterator并不是一个顶层const,而auto只有顶层const和&复合类型无法自动转换。

c++11为了方便加入了两种新函数,分别是cbegin()和cend(),使用这两个函数无论其对象本身是否为常量对象其返回结果都是const_iterator。

warning:但凡是使用了迭代器的循环体,都不要向迭代器所属的容器中添加元素。

数组

使用数组初始化vector对象

数组无法为另一个内置类型的数组赋值,也不允许用vector对象初始化数组,但是可以使用数组初始化vector对象。

int int_arr[]={0,1,2,3,4,5};
vector<int> ivec(begin(int_arr),end(int_arr));

也可以使用部分数组的元素初始化vector容器

vector<int> sebvec(int_arr+1,int_arr+4);

表达式

求值顺序

优先级规定了运算对象的组合方式,但是并没有说明运算对象按照什么顺序求值。

int i=0;
cout<<i<<" "<<++i;

//运算结果为:1 1

为什么呢?是不是特别别扭的感觉,因为<<运算符没有明确规定何时以及如何对运算对象求值,因此这个输出表达式是未定义的,所以我们无法推断它的行为,编译器可能先求++i,也可能先求i。(但是我反正运行了好几遍都没问题,都是1 1,可能是vs太高级了吧)

位运算符

warning:关于符号位如何处理没有明确规定,所以强烈建议仅将位运算符用于处理无符号类型。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值