c++课堂学习总结(4,5,6)

杂项

constexpr对象
如果认定某个对象是常量表达式,可以把它声明为constexpr类型

constexpr int size = 20;  // 20是常量表达式
constexpr int limits = size + 10; // size+10是常量表达式

auto
用auto声明变量的类型,由编译器去自动分析表达式的类型,推断出变量的实际类型
注意:定义auto变量必须有初始值

auto x = 5;    //5是int类型,所以x是int类型
auto name = "world";  //name要保存字符串"world"
     //具体类型不清楚
错误范例
auto a;    //错误:没有初始值
auto r = 1, pi = 3.14;  //错误:r和pi推断出的类型不一致

range-for语句
range-for是一种简化的for语句
从头至尾对容器或序列的所有元素逐个执行某种操作

for(定义一个变量,表示序列中的每个元素 : 要处理的序列(初始值列表、数组、vector或string等类型的对象) )
	一条语句或语句块,表示对元素的处理代码;
// 累加20以内的素数
int sum = 0;
for(int e : {2, 3, 5, 7, 11, 13, 17, 19})
 sum += e;
cout << sum << endl;  //输出77
// 将数组的每个元素加倍
int arr[] = {1, 3, 5, 7, 9};
for(auto ele : arr) {
 ele = ele * 2;     
 cout << ele << " ";    
}     // 输出:2 6 10 14 18

指针和引用

指针指向内存地址,指针可以间接操纵它指向的对象。
使用

类型 *指针变量;
int  *pi;
int* pi; 
char *pc1, *pc2;
char* pc1, pc2; 
char *pc1, pc2; 
char* pc1, *pc2;

指针存放指定类型对象的地址,要获取对象的地址,使用取地址运算符“&”

int ival = 120;
int  *pi = &ival; 
  // pi存放int变量ival的地址
  // 或者说pi指向ival
char ch = 'a', *pc = &ch;
  // pc指向字符型变量ch

对于一个指针 int *pi
*pi是指向的量的值,pi是指向的量的地址。
常见错误

int ival = 100;
int *pi = &ival;   // pi 被初始化为ival的地址
int *pi2 = ival;       // 编译错误,ival不是地址
double dval = 1.5;
pi = &dval;   // 编译错误,pi是int类型。
pi2 = 0;        // 正确:pi2是空指针

指针值为0时是一个空指针,即不指向任何对象的指针

// 生成空指针的3种方法
int *p1 = nullptr;  
int *p2 = 0;       
int *p3 = NULL;

void*指针
可以持有任何类型的地址值,即通用指针
相关的值是个地址,但是该地址保存的对象类型不知道
不能操纵void指针指向的对象,只能传送该地址值或者和其他地址值进行比较
不允许void指针到其他类型指针的直接赋值

动态内存分配

过new和delete运算符进行动态存储
new表达式的三种形式
分配单个对象:new 类型 或者 new 类型(初始值)
分配多个连续存储的对象:new 类型[数组大小]
定位new,在指定位置分配空间:new (指针) 类型;

int* ip1 = new int;
   //在堆上分配一个int类型的对象,返回它的地址
*ip1 = 512; 
   //堆上分配的这个对象只能通过指针间接操作
int* ip2 = new int(100);
   //在堆上分配一个int对象,
   //初始化为100,返回其地址
   int* ipa = new int[100];
//在堆上分配一个大小为100的int数组并返回数组的首地址
   

释放new分配的单个对象的delete形式
delete 指针;
释放new分配的数组的delete形式
delete[] 指针;

int* ip = new int;
... //不再使用这个int对象时,释放内存
delete ip; 
//释放指针指向的int对象
int* pa = new int[100]; 
... //不再使用这个数组时,释放内存
delete[] pa; 
//释放指针pa指向的数组

左值引用
左值引用又称为别名,它可以作为对象的另一个名字
通过左值引用可以间接地操纵对象

int ival = 100;
int &refVal = ival;

可以理解为把这两个东西绑定在一起,无论通过哪一个名字都可以对这个进行操作。

int x = 100, y = 20;
int &r = x; // r是x的引用
r = y;  // 这是x = y

右值引用
在赋值表达式中,出现在赋值号左边的就是左值,而在赋值号右边的则称为右值
可以取地址的、有名字的就是左值;不能取地址的、没有名字的就是右值
例子

int i = 42;
int &r = i;   //正确:左值引用
int &&rr1 = 10;  //右值引用

std::move
像对待右值那样处理一个左值

int &&rr1 = 10;   //右值引用
int &&rr2 = rr1;   //错误
int &&rr3 = std::move(rr1); //正确

const与指针
const int *pi //指向的地址的内容是常量,内容不可变。
int const *pi //指向的地址是常量,地址不可变。

结构体struct
struct 结构体类型名{
成员声明;
};
这玩意我用的太熟悉了(笑)

//定义结构体类型X
struct X { 
char c;
int i;
float f;
double d;
};  
//定义X类型的变量 
X s1, s2;  
//通过X变量s1引用成员c
s1.c = 'a';   
s1.i = 1;
s1.f = 3.5;
s1.d = 0.7
//通过指针ps引用成员c
X* ps = &s1;
s2.c = ps->c;

枚举
枚举类型定义了一组命名的整数常量,以提高代码的可读性
enum TrafficLight { red, green, yellow };
TrafficLight枚举类型定义了3个常量:0,1,2 分别和名字red,green以及yellow关联。
TrafficLight是一个枚举类型,可以用来定义枚举变量,变量的值只能是枚举成员。
TrafficLight stop = red;

数组与指针
使用数组时一般会转换为指针
ia是一个int*类型的指针常量
ia和&ia[0]都表示数组第一个元素的地址

int a[10];
//指针访问数组元素
for(int *p = a; p < a+10; p++)
 cout << *p;

数组元素和地址
一维数组元素在内存中按下标顺序依次存放
一维数组a[n]的元素a[i]在内存中地址是a+i。
多维数组在内存中按行序存储
二维数组a[m][n]的元素a[i][j] 在内存中的地址是a+(i*n+j)

begin()和end()
分别指向数组第一个元素地址与最后一个元素的地址的下一个的地址。
和迭代器在一起很好用

string
字符串类型
基本使用

#include <iostream>
#include <string>
using namespace std;
int main(){
string s1, s2;   //创建两个空字符串对象
string s3 = "Hello, World!"; //创建s3,并初始化
string s4("I am ");
s2 = "Today";    //赋值
s1 = s3 + " " + s4;   //字符串连接
s1 += " 5 ";    //末尾追加
cout << s1 + s2 + "!" <<endl; //输出字符串内容
cout <<"Length of s1 is :" << s1.size() << endl;
//逐个输出s1中的字符
for (size_t i = 0; i < s1.size(); ++i) 
cout << s1[i] <<" ";   
}

一般操作与其他一样,下面介绍一些特殊操作。
getline()函数

//每次读取一行文本,直到文件结束
string line;
while(getline(cin, line))
  cout << line << endl;

头文件中有很多对字符的操作,在这里不再赘述。

vector动态数组
相当于可变大小的数组。
定义和初始化

//默认初始化
vector<string> svec;   // svec不含任何元素
vector<int>  ivec;   // 空的vector
//拷贝初始化
vector<int>  ivec2(ivec);  // 把ivec的元素复制给ivec2
vector<int>  ivec3 = ivec;  // 把ivec的元素复制给ivec3
vector<string> svec(ivec2); // 错误:元素类型不同
//列表初始化
//列表初始化
vector<string> svec1{"how", "are", "you"};  //3个元素
vector<string> svec2("how", "are", "you");  //错误:括号
//创建指定数量的元素
vector<int> ivec(10, 1);  //ivec包含10个int元素都初始化为1
vector<string> svec(10, "hi"); //10个string对象"hi"
//值初始化
vector<int> ivec(10);  //ivec包含10个int元素都初始化为0
vector<string> svec(10); //svec包含10个string对象,默认空
//注意容易混淆的问题:各种括号
vector<int> v1(10);//v1有10个int元素,每个都初始化为0
vector<int> v2{10}; //v2有1个元素,值是10
vector<int> v3[10]; //v3是有10个元素的数组
    //每个元素都是一个空vector对象
vector<int> v4(10, 1); //v4有10个int元素,每个都初始化为1
vector<int> v5{10, 1}; //v5有2个int元素,值分别是10和1

向内放入元素
v.push_back(a);
元素个数
v.size()
对vector的数组可以与普通数组一样的按下标访问。
a[i] v[i];

迭代器
定义
vector :: iterator it;
使用
for(it.begin();it!=it.end();it++)

文件读写
1)新建一个文件流对象
读文件用ifstream,写文件用ofstream;
(2)把文件流对象和文件关联起来
打开文件,使用文件流的open函数;
可以指定打开文件的模式
(3)操作文件流
使用与终端I/O相同的操作读写文件
(4)关闭文件流
使用文件流的close函数

#include <fstream>  //文件流标准库头文件
using namespace std;
int main(){
 ifstream in("numbers.txt");
 int number, sum = 0;
 in >> number;
 while(number != 0){
  sum += number;
  in >> number;
 }
 in.close();
 ofstream out;
 out.open("output.txt"); 
 out << "sum is: " << sum << endl;
 out.close();

字符串流
istringstream:从string对象中读取数据
ostringstream:向string对象写入格式化的内容
stringstream:从字符串读取数据,将数据写入字符串
输入缓冲区
从输入流一次性读取一大块数据,以字符串形式保存在istringstream对象中,再用适当的输入操作逐项从istringstream对象中提取各个数据项
输出格式化
将所有要输出的内容先用输出操作写到ostringstream对象上,再一次性的将这个ostringstream对象中保存的字符串写入输出流

//利用istringstream,读取并处理一行学生记录的代码
//从输入文件流in中读一行学生成绩并存入scoreSheet中
ScoreItem item; 
string buffer;    
getline(in, buffer); 
istringstream is(buffer); 
is >> item.name;
int score;
while(is >> score)
 item.scores.push_back(score);
scoreSheet.push_back(item);
/利用ostringstream构造并输出一个学生的统计信息的代码
//处理scoreSheet中的一个元素,即一个学生的记录
ostringstream format(""); 
format << (it -> name) << " "; 
//按格式将数据继续写入format
format.precision(1);  //小数点后保留1位
format << sum << " " 
  << courseNumber << " " 
  << fixed << average;
//构造的输出信息格式为:姓名 总成绩 课程门数 平均成绩
out << format.str() << endl; 
    //一次性写入输出文件流out中

函数

函数是一个命名的代码块,通过调用函数可以执行相应的代码
用来实现各种算法,完成特定的任务
库函数,程序员自定义的函数
定义一个阶乘函数

int fact(int n)
{      //函数体开始
int ret = 1;
while ( n > 1 )  //求阶乘
 ret *= n--;
return ret;   //返回结果
}     //函数体结束

函数必须指定一个返回类型
如果函数不返回任何结果,将其返回类型声明为void

//不返回值的函数
void print(int value)
{ … }

函数调用

例子

int main(){ 
int val = fact(5);
cout << "5! = " << val;
}

函数定义中的参数被称为形式参数,简称形参
在调用函数时提供的数据称为实际参数,简称实参
函数调用时提供的实参个数和类型必须与函数定义中的形参个数和类型匹配
编译器会进行参数类型检查,尝试自动类型转换

函数声明
函数在使用之前必须声明
一个函数可以在程序中多次声明
函数定义也可以被用作声明,但是函数在程序中只能定义一次

参数传递
参数传递方式
传值
把实参的值传给形参,实参不受函数过程中的任何影响。

int test(int left, int right){
      return left + right;
}
int main(){
     int lval = 2;
     int rval = 3;
     int result = test( lval , rval );
}

传指针
使用指针参数是传地址值

void pswap(int *pv1, int *pv2){ 
int t;
t = *pv1;  *pv1 = *pv2;  *pv2 = t;  //间接访问
}
int main(){
int ival1 = 10, ival2 = 20;
int *p1 = &ival1;
int *p2 = &ival2;
cout << ival1 << " " << ival2 <<endl;
pswap(p1,p2);
cout << ival1 << " " << ival2 <<endl;
}
特点
简单直接
不会改变实参的内容

按值传递参数不适合的情况

大型的类对象或结构体变量作为实参时
在运行栈中分配空间并复制对象,时间和空间开销过大
必须要修改实参的值时,如交换两个变量的值

传引用
此时,引用形参绑定到实参,是实参对象的别名,函数内的变化会造成影响

//rswap将交换rv1和rv2的值
void rswap(int& rv1, int& rv2) {
int t;
t = rv1;
rv1 = rv2;
rv2 = t;
}

特点
当参数是引用时,函数接收的是实参的左值而不是值的副本
即形参是实参的引用,或者说是别名。
函数操纵的形参是实参的别名,因而可以改变实参的值

如何选择
对于内置类型的小对象,传值的方式更简单直接
如果想在函数内改变实参,使用传引用或传指针的方式
传指针的语法比引用复杂一些,但使用起来更清晰明确
对于类类型的对象,尽量使用引用传递参数,效率更高
使用const限定可以避免实参被修改

数组参数
数组作参数时,将传递数组第一个元素的地址

void foo( int *a );
void foo( int a[] );
void foo( int a[10] );

返回值
返回引用
将函数声明为返回引用,不需要对return语句中的表达式进行复制,而是返回对象本身

//找出s1和s2中比较短的一个并返回其引用
const string& shorter(const string& s1, const string& s2)
{ 
return (s1.size() <= s2.size()) ? s1 : s2;
}
//函数返回结果时不会真正复制对象,返回的就是s1或s2本身。

函数重载
如果同一个作用域内的几个函数名字相同但形参列表不同,则它们是重载函数

void print(const int *b, const int *e){...}
void print(const int ia[], size_t size){...}
void print(const char *cp){...}

调用重载函数时,编译器会根据实参的类型推断出要调用的是哪个函数

int arr[5] = {1, 2, 3, 4, 5};
print("Hello!");
   //调用print(const char *)
print(arr, 5);
   //调用print(const int*, size_t)
print(begin(arr), end(arr));
   //调用print(const int*, const int*)

重载函数的参数表必须不同
或参数个数不同,或参数类型不同
返回类型不能区分两个重载函数
因为调用函数时可以忽略函数的返回值
const限定词对形参类型的影响
非指针和引用上的const限定词不能区分重载函数
const限定指针或引用时,可以实现函数重载

// 同一函数的重复声明
int foo(int);   //内置类型
int foo(const int);  //同一函数的重复声明
//重载函数
void foo(string& str);  //非const引用
void foo(const string& str); //const引用
int goo(int*);   //非const指针
int goo(const int*);  //const指针

调用函数时,如果存在多个重载函数,编译器将根据函数调用中指定的实参进行选择
用实参的类型和个数与函数的形参进行匹配
可能应用隐式类型转换

作用域和存储类别
生存期、作用域和存储类别
对象的生存期
是指程序执行过程中对象存在的时间
对象的生存期与对象的作用域和存储类别密切相关
名字的作用域
程序的一段区域,名字的作用域指的是该名字可以在哪些程序文本区使用。
对象的存储类别
创建对象时分配内存空间的方式和内存空间的类型

全局变量和全局函数
在全局作用域中可以定义函数和变量
在程序整个执行过程中都存在,可以在整个程序中使用
全局的内置类型变量,不指定初始值时被初始化为0值

局部变量
局部对象的作用域从其声明点开始,到函数结束处为止
在语句块中声明的对象具有块作用域,只在该语句块中可见

存储类别和存储空间分配
C++程序中对象的存储类别有
静态存储
自动存储
动态存储

静态分配是指在全局静态存储区为变量分配内存空间
编译时就分配了内存地址(相对地址)
在程序开始执行时变量就占用内存,直到程序结束时变量才释放内存
静态存储的对象
全局对象、namespace作用域对象、文件作用域对象、用static 和extern 声明的对象、局部static对象
静态存储对象的生存期从被创建持续到程序结束

自动分配指在程序的运行栈存储区中为变量临时分配内存空间
程序运行后,在变量作用域开始时由系统自动为变量分配内存,在作用域结束后即释放内存
自动存储的对象
自动局部对象、register 局部对象
自动存储的对象的生存期持续到创建它们的块结束时为止

动态分配是指利用被称为堆(heap)的内存块为变量分配内存空间
堆在静态存储区和栈之外,又称为自由存储区
动态分配的对象
完全由程序本身控制动态空间的分配与释放
用new 创建动态对象,用delete 结束这类对象的生存期
堆中分配的对象没有名字,需要通过指针间接操纵
由new 分配的动态对象不再使用时必须用delete 释放,否则会造成内存泄漏

类和对象

类的定义
抽象数据类型(ADT)
C++允许用户以类的形式自定义数据类型,反映待解决问题中的各种概念,以更自然的方式编写程序。
数据抽象
接口和实现分离
类的接口包括用户能执行的操作
类的实现包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数

抽象数据类型由两部分组成
一组数据
对数据的操作
操作向程序的其余部分展现了数据是怎么样的,程序的其余部分则通过操作改变这些数据
C++基本语言定义的抽象数据类型
结构体,描述数据
全局函数,描述对数据的操作,数据以参数的形式传递给函数

C++扩展了struct的概念,使之可以包含函数作为成员
结构体内的函数被称为成员函数,结构体中的数据则称为数据成员
扩展的结构体被称为类,结构体类型的变量被称为对象

struct SalesData{ // 商品销售数据类的定义
//数据成员声明
string productNo;
double price;
unsigned unitSold;
//成员函数声明和定义
double totalRevenue(){return price * unitSold;}
void read(){ cin >> productNo >> price >> unitSold; }
void print(){…}

成员函数直接在类内定义,也可以在类外定义

struct SalesData{…  //数据成员声明略
//成员函数声明
double totalRevenue();
void read();
void print();
};
//成员函数的类外定义
double SalesData::totalRevenue()
{ return price * unitSold;}
void SalesData::read()
{ cin >> productNo >> price>> unitSold;}
void SalesData::print()
{ cout << productNo<< ":"<< price<<" "<< unitSold
 << " " << totalRevenue() << endl;}

猜你喜欢

转载自blog.csdn.net/qq_17679843/article/details/88779429