C++学习笔记(更新)

前言

本篇文章是个人的C++学习记录,也算是《C++ Primer Plus》的读书笔记,如有错误,期望指正

第一章 预备知识

类和对象

在c++中,类是一种规范,描述了一种新型数据格式,对象是根据这种规范构造的特定数据结构

也就是说,类是抽象的,描述了有什么数据?对数据有哪些操作?而对象是具体的,是类的实例化

OOP(面向对象编程)并不仅仅是将数据和方法合并成类定义。

  1. 创建可重用的代码,这将减少大量的工作
  2. 信息隐藏可以保护数据,使其免遭不适当的访问
  3. 多态使得我们可以为运算符和函数创建多个定义,通过编程上下文来确定使用哪个定义
  4. 继承使得我们能够使用旧类派生出新类

泛型

模板是一种对类型进行参数化的工具,模板是泛型编程的基础,而泛型编程指的就是编写与类型无关的代码,是C++中一种常见的代码复用方式。模板分为模板函数模板类模板函数针对参数类型不同的函数;模板类主要针对数据成员和成员函数类型不同的类。
在这里插入图片描述

以上是对C++一些相较于C而言比较“新”的东西的总结,也是作者接下来要重点学习的地方

第二章 C++ 内置数据类型

基本类型

整型变量:bool、char、signed char、unsigned char、short、 unsigned short、int、unsigned int、long、unsigned long

浮点型变量:float、double、long double

对于整型变量,头文件climits中定义了符号常量来表示类型的属性,可自行查看。

对于浮点类型,只要包含头文件“stdio.h”即可使用printf进行格式化输出。

C++算术运算符

+、-、*、/、%分别为加、减、乘、除、求余

对于除法,分别有int类型、long类型、double类型、float类型

#include<iostream>
#include"stdio.h"
using namespace std;
int main(){
    int a=2,b=5;
    float c=3.78343;
    cout<<a/b<<" ";//整数除法
    cout<<2.0f/5.0f<<" ";//错误用法:af/bf、2f/5f
    printf("%.2f",c);
    return 0;
}
输出:0 0.4 3.78

复合类型

数组、C风格字符串、string类字符串、结构体、共用体、枚举、指针、vector类、array类……

数组

声明数组,需要指出:储存在每个元素中的值的类型、数组名、数组中的元素数

typeName arrayName[arraySize];
//其中,arraySize指定元素数目,它必须是整型常数或const值,也可以是常量表达式。简而言之,不能是变量(程序运行过程赋值的量)
//数组是由其他类型来创建的复合类型,不能仅仅将某种东西声明为数组,它必须是特定类型的数组。

初始化数组,只有在定义数字时才能使用初始化,以后就不能使用了,也不能把一个数组赋给另一个数组,但可以用下标给元素赋值

int text[2] = {1,2};	float b[5] = {1,2,3};//可以不完全赋值,但不能超额
short things[] = {1,5,3,8};//计算元素个数时,可以用
int num = sizeof things / sizeof (short);

数组赋值

int main(){
    int a[10];char b[10];string c[10];
    cin>>a;//报错
    cin>>b;//允许
    cin>>c;//报错
    return 0;//a=,b=,c=,也报错,不能整体赋值,只能单元素赋值
}//字符串(字符数组)可以整体cin,其他数组只能单元素cin

C风格字符串

C风格字符串的本质是一个以空字符’\0’为结尾的char数组,将字符串存储到数组中,有两种方式

  1. 将数组初始化为字符串常量
char dog[8]={'f','a','t','e','s','s','a','\0'}
char bird[11]="Mr.Cheeps"//后两种方式会自动在结尾添加'\0'
char fish[]="Bubbles"//两种方式都要求数组长度大于等于字符串加'\0'的长度
//注意,不能先定义再赋值,如:char a[10];a="XXX";//a是数组第一个元素的指针,不能改变值。
//但是可以对单个元素赋值,如:char a[10];a[0]='x';

拼接字符串,在cout时,任何两个由空白(空格、制表符、换行符)分隔的字符串常量,都会自动拼接成一个。

cout<<"sfsagsg""sagsagasg";cout<<"sagasghsahas"	"asfagasgsa";
cout<<"safsagasdhrwjnf"
"sagasghshdahna";
  1. 将键盘或文件输入读入到数组中
const int Arsize=20;
char name[Arsize];
char dessert[Arsize];
cin>>name;cin>>dessert;
cout<<"hello"<<name<<"I have some"<<dessert<<"for you";
cout<<strlen(name);
//当使用cin直接输入时,cin会将空白(空格、制表符、换行符)识别为空字符,确定字符串的结束位置
//如果名字中带空格,那么就会把被识别为两个字符串。如“hall sder”识别为“hall”和“sder”分别录入name和dessert
//strlen函数返回字符串的真实长度(不算空字符)
//使用strlen需要#include<cstring>

面向行的输入

const int Arsize=20;
char name[Arsize];char dog[Arsize];
cin.getline(name,20);//遇见换行符或者到达最大值(算上换行)停止,吃掉换行符,并把它变成空字符
cin.get(dog,20);//遇见换行符或者到达最大值(算上换行)停止,不吃换行符,把它留在输入流中
cin.get();//接收留在输入流中的换行符
cin.get(name,Arsize).get();cin.get(dog,Arsize).get();//与cin.getline(name,Arsize).getline(dog,Arsize);等效
(cin>>name).get();//cin>>name返回的是cin对象,可以调用get()

string类

#include<iostream>
#include<string>
using namespace std;
int main(){//string对象在很多方面与字符数组相同,初始化方式可以一样,可以cin,可以cout,也可以用数组表示法访问单个字符
    string str1;string str2="asdfg";
    str1="sdafasf";str2="asdfgg";
    cout<<str2<<" "<<str1<<" ";
    str1=str2;
    cout<<str1[3];
    return 0;
}//可以将string类字符视为一个变量,赋值、初始化之类的非常方便
asdfgg sdafasf f
void tip(){
	string str;
    getline(cin,str);//用于整行输入(包括空格)
}
运算符重载
  1. + 和 +=:连接字符串
  2. =:字符串赋值
  3. >、>=、< 和 <=:字符串比较(例如a < b, aa < ab)
  4. ==、!=:比较字符串
  5. <<、>>:输出、输入字符串

注意:使用重载的运算符 + 时,必须保证前两个操作数至少有一个为 string 类型。例如,下面的写法是不合法的:

#include <iostream>
#include <string>
int main()
{
    string str = "cat";
    cout << "apple" + "boy" + str; // illegal!
    return 0;
}
查找
string str;
cin >> str;

str.find("ab");//返回字符串 ab 在 str 的位置
str.find("ab", 2);//在 str[2]~str[n-1] 范围内查找并返回字符串 ab 在 str 的位置
str.rfind("ab", 2);//在 str[0]~str[2] 范围内查找并返回字符串 ab 在 str 的位置

//first 系列函数
str.find_first_of("apple");//返回 apple 中任何一个字符首次在 str 中出现的位置
str.find_first_of("apple", 2);//返回 apple 中任何一个字符首次在 str[2]~str[n-1] 范围中出现的位置
str.find_first_not_of("apple");//返回除 apple 以外的任何一个字符在 str 中首次出现的位置
str.find_first_not_of("apple", 2);//返回除 apple 以外的任何一个字符在 str[2]~str[n-1] 范围中首次出现的位置

//last 系列函数
str.find_last_of("apple");//返回 apple 中任何一个字符最后一次在 str 中出现的位置
str.find_last_of("apple", 2);//返回 apple 中任何一个字符最后一次在 str[0]~str[2] 范围中出现的位置
str.find_last_not_of("apple");//返回除 apple 以外的任何一个字符在 str 中最后一次出现的位置
str.find_last_not_of("apple", 2);//返回除 apple 以外的任何一个字符在 str[0]~str[2] 范围中最后一次出现的位置

//以上函数如果没有找到,均返回string::npos
cout << string::npos;
子串
str.substr(3); //返回 [3] 及以后的子串
str.substr(2, 4); //返回 str[2]~str[2+(4-1)] 子串(即从[2]开始4个字符组成的字符串)
替换
str.replace(2, 4, "sz");//返回把 [2]~[2+(4-1)] 的内容替换为 "sz" 后的新字符串
str.replace(2, 4, "abcd", 3);//返回把 [2]~[2+(4-1)] 的内容替换为 "abcd" 的前3个字符后的新字符串
插入
str.insert(2, "sz");//从 [2] 位置开始添加字符串 "sz",并返回形成的新字符串
str.insert(2, "abcd", 3);//从 [2] 位置开始添加字符串 "abcd" 的前 3 个字符,并返回形成的新字符串
str.insert(2, "abcd", 1, 3);//从 [2] 位置开始添加字符串 "abcd" 的前 [2]~[2+(3-1)] 个字符,并返回形成的新字符串
追加

除了用重载的 + 操作符,还可以使用函数来完成。

str.push_back('a');//在 str 末尾添加字符'a'
str.append("abc");//在 str 末尾添加字符串"abc"
删除
str.erase(3);//删除 [3] 及以后的字符,并返回新字符串
str.erase(3, 5);//删除从 [3] 开始的 5 个字符,并返回新字符串
交换
str1.swap(str2);//把 str1 与 str2 交换
其他
str.size();//返回字符串长度
str.length();//返回字符串长度
str.empty();//检查 str 是否为空,为空返回 1,否则返回 0
str[n];//存取 str 第 n + 1 个字符
str.at(n);//存取 str 第 n + 1 个字符(如果溢出会抛出异常)
实例

查找给定字符串并把相应子串替换为另一给定字符串

string 并没有提供这样的函数,所以我们自己来实现。由于给定字符串可能出现多次,所以需要用到 find() 成员函数的第二个参数,每次查找之后,从找到位置往后继续搜索。直接看代码(这个函数返回替换的次数,如果返回值是 0 说明没有替换):

int str_replace(string &str, const string &src, const string &dest)
{
    int counter = 0;
    string::size_type pos = 0;
    while ((pos = str.find(src, pos)) != string::npos) {
        str.replace(pos, src.size(), dest);
        ++counter;
        pos += dest.size();
    }
    return counter;
}

从给定字符串中删除一给定字串

方法和上面相似,内部使用 erase() 完成。代码:

int str_erase(string &str, const string src)
{
    int counter = 0;
    string::size_type pos = 0;
    while ((pos = str.find(src, pos)) != string::npos) {
        str.erase(pos, src.size());
        ++counter;
    }
    return counter;
}

给定一字符串和一字符集,从字符串剔除字符集中的任意字符

int str_wash(string &str, const string src)
{
    int counter = 0;
    string::size_type pos = 0;
    while ((pos = str.find_first_of(src, pos)) != string::npos) {
        str.erase(pos, 1);
        ++counter;
    }
    return counter;
}

结构体

数组可以将多个同类型元素存储在一起,而结构体可以将多种类型的数据存储在一起

struct text{
    char name[20];
    float volume;
    double price;
};//结构体由其他类型数据组成,各类型依然遵守原来的用法,初始化方法、赋值方法……
int main(){//可以将一个结构赋给另一个结构(他们的类型相同时)
    text a={};
    //a={"asdad","2.5","2.5"};和大多数(除了string之外)混合类型一样,不能“赋值”
    cin>>a.name;//可以单元素cin,不能整体cin(cin>>a)
    text b={"sadfa",2.5,2.5};//初始化方式这样
    //cout<<b;不能直接cout一整个
    cout<<b.name<<" "<<a.name;
    return 0;//asfaf
}			//sadfa asfaf

共用体

共用体可以存储不同的数据类型,但只能存储其中的一种类型

union one4all{
    int int_val;
    long long_val;
    double double_val;
}pail;//可以用pail存储int、long、double中的一种,条件是在不同的时间进行。

共用体与结构体共用

struct wight{
    char brand[20];int type;
    union id{
        long id_num;
        char id_char[20];
    }id_val;//id_val为组成部分,调用时要通过id_val,id_num,id_char为id_val成员,一个有值另一个就会没值
};
struct other{
    char brand[20];int type;
    union {
        long id_num;
        char id_char[20];
    };//匿名共用体,id_num,id_char为结构体成员,一个有值另一个就会没值
};//匿名匿的是共用体和共用体变量的名,不单单是共用体变量的名
int main(){
    wight a={};other b={};
    cin>>a.id_val.id_char;
    cin>>b.id_num;
    return 0;//这里共用的作用是,可以根据type判断id是那种类型,这样就减少了空间占用。
}

枚举

enum spectrum {red,orange,yellow,green,blue,violet,indigo,ultraviolet};
//让spectrum成为新类型的名称,它被称为枚举,花括号里的东西变成符号常量
spectrum a;//则a只能用花括号里的东西赋值
enum bits {one=1,two=2,three=3};
enum {on=1,tw=2,thre};//枚举相当于多个const,但是值只能是整数

指针和NEW

指针
#include<iostream>
using namespace std;
int main(){
   int a=3;int* b=&a;//类型名* 指针名=&变量名
   cout<<a<<" "<<b<<" "<<*b<<" "<<&a;
   return 0;//int*有两种理解方式, int* a 强调 a 是指向int的指针, int *a 强调 *a 是int变量,两种方式本质上没有差别
}//注意,int* p1,p2;将创建一个指针(p1)和一个int变量(p2)
3 0x22fe44 3 0x22fe44
//使用 int *a=&b; 这种格式的时候,要注意'&b'的值赋给了'a'而不是'*a'
int a=80;
const int* text0=&a;//指向 const int(常量)的指针
int* const test1=&a;//指向 int 的 const 的指针(指针本身不能被改变)
//注意,可以将const数据或非const数据的地址赋给指向const的指针(不能通过指向const的指针修改数据)
//但是不能将const数据的地址赋给非const指针
指针、数组、字符串
int main(){
    int num_text[5]={1,3,5,7,9};
    char char_text[6]={'a','b','c','d','e','\0'};
    int* nt=num_text;char* ct=char_text;
    cout<<nt<<" "<<ct<<" "<<num_text<<" "<<char_text<<"\n";
    cout<<*nt<<" "<<*ct<<" "<<*nt+1<<" "<<*(nt+1);
    return 0;
}//输出:0x22fe20 abcde 0x22fe20 abcde
		1 a 2 3

对于数组与字符串而言,指针名和数组名都是指向第一个元素的指针,但是cout字符串指针时,会打印出整个字符串

使用 *指针 等效于变量名, *(指针+1)意味着下一个元素,即text[3] == *(te+3)

new 与 delete
typeName * pointer_name = new typeName
//程序员告诉new要一个什么类型的内存,new就会找到这样一个内存块,并返回它的指针
#include<iostream>
using namespace std;
int main(){
    int n;cin>>n;
    int* int_point = new int;
    int* ints_point = new int[n];
    char* char_point = new char;
    char* chars_point = new char[n];//new可以用来创建动态数组
    delete [] chars_point;
    delete [] ints_point;
    delete int_point;
    delete char_point;
}//new完不要忘记delete

使用new创建动态结构

struct test{
    int num;
    char name;
};

int main(){
    auto* st = new test;
    cin>>st->name;
    cin>>(*st).num;
    cout<<(*st).name<<" "<<st->num;
    return 0;
}
d 3
d 3
int main(){
    auto* st = new test[3];
    cin>>(st+1)->name;
    cin>>(*(st+1)).num;
    cout<<(*(st+1)).name<<" "<<(st+1)->num;
    return 0;
}
d 3
d 3

数组的替代品

模板类 vector

https://www.runoob.com/w3cnote/cpp-vector-container-analysis.html

引用变量(见函数探幽部分)

第三章 循环与关系表达式

for 循环

while循环

do…while循环

第四章 函数

C++自带了一个包含函数的大型库(标准ANSI库加上多个C++类),但真正的编程乐趣在于编写自己的函数

构建函数

  1. 提供函数定义
void functionName(parameterList)//参数列表,要说清楚参数类型和参数名
{
	wuliwala;		
	return xxx;		//可选
}

typeName functionName(parameterList)
{
	wuliwala;
	return xxx;		//必须和typeName一样
}
  1. 提供函数原型
void functionName(parameterList);//参数列表可以只写参数类型
typeName functionName(parameterList);//在使用函数之前声明(一般放在main()之前)
  1. 调用函数
functionName(实参(用逗号隔开));

注意

C++对于返回值的类型有一定的限制:不能是数组,但可以是其他任何类型(整数、浮点数、指针、甚至可以是结构和对象)

但是,C++可以将数组作为结构或对象的组成部分来返回。另外,C++可以以任何数据类型为参数

void test0(int a){//以变量为参数,函数体内用的是实参的副本,副本改变不会影响实参
    cin>>a;
    cout<<a<<"\n";
};

void test1(int* a){//Clang-Tidy: Pointer parameter 'a' can be pointer to const
    cin>>*a;//以指针为参数,通过指针会影响实参
    cout<<*a<<"\n";
}

void test2(const int* a){
    //"cin>>*a;"错误,不能通过const的指针修改指向值
    cout<<*a<<"\n";
}
//const可以保护指针指向的内容,确保它不会在函数里被修改,数组的指针也可以保护,啥都能保护
int main(){
    int b;
    int* c=&b;
    test1(c);
    cout<<b;
}
123
123
123//实参b的值已经通过指针被函数改变
int test0(int* a){
    cin>>a[0];
    cin>>*(a+1);
    return a[0]+1;
}
//指针会被函数当做数组名直接用
int test1(int a[]){
    cin>>a[0];
    cin>>*(a+1);
    return a[0]+1;
}
//如果实际想操作的不是数组,建议不要用指针,直接用int test(int a){}比较好
int main(){
    int a[3]={1,2,3};
    cout<<test0(a)<<"\n";
    cout<<test1(a);
    return 0;
}
5 6 7 8
6
8
int a=80;
const int* text0=&a;//指向 const int(常量)的指针
int* const test1=&a;//指向 int 的 const 的指针(指针本身不能被改变)
//注意,可以将const数据或非const数据的地址赋给指向const的指针(不能通过指向const的指针修改数据)
//但是不能将const数据的地址赋给非const指针
return 0;

使用数组区间的函数/函数与字符串

int sum_arr(const int* begin,const int* end){
    const int* pt;
    int total=0;
    for(pt=begin;pt!=end;pt++){
        total = total + *pt;
    }
    return total;
}
int main(){
    int a[5]={1,2,3,4,5};
    cout<<sum_arr(a,a+2);
    return 0;
}
3
char* builder(char c,int n){
    char* pstr = new char[n+1];
    pstr[n]='\0';
    while (n-->0){
        pstr[n]='c';
    }
    return pstr;
}

int main(){
    char* ps=builder('d',3);
    //delete[] pstr;不能解放pstr,它在函数里面,应该用ps把它接出来,然后解放
    cout<<ps;
    delete[] ps;
    return 0;
}

函数与结构

struct time{
    int hours;
    int mins;
};
time sum(time m,time n){
    time total={0,0};
    total.hours=m.hours+n.hours;
    total.mins=m.mins+n.mins;
    return total;
}
int main(){
    time m = {3,2};
    time n = {4,6};
    time s = sum(m,n);
    cout<<s.mins<<" "<<s.hours<<" ";
    return 0;
}

函数与二维数组

int sum(int ar2[][4],int size){
    int total=0;
    for(int r=0;r<size;r++){
        for(int c=0;c<4;c++){
            total+=ar2[r][c];
        }
    }
    return total;
}
int main(){
    int data[3][4]={{1,2,3,4},{9,8,7,6},{2,4,6,8}};
    //data是一个数组名,该数组有三个元素,每个元素都是由4个int组成的数组
    //也就是说,每个元素都是一个指针,指向由4个int组成的数组的指针
    //以data为参数时,应该这样描述:int (*ar2)[4] 或者 int ar2[][4]
    //这时,我们要对行数用另一个参数加以限制
    int sums=sum(data,2);
    cout<<sums;
    return 0;
}

函数与string

void display(const string sa[],int n){
    for(int i=0;i<n;i++){
        cout<<i+1<<":"<<sa[i]<<endl;
    }
}
int main(){
    string list[6];
    for(int i=0;i<6;i++){							//1:sadgdh	2:dsgsdg	3:asg	
        cout<<i+1<<":";                             //4:gjh		5:fdh		6:fdh
        getline(cin,list[i]);                       //1:sadgdh
    }                                               //2:dsgsdg
    display(list,3);                                //3:asg
    return 0;                                       
}                                                   

递归

含义

第一要素:明确函数想要干什么

对于递归,我觉得很重要的一个事就是,这个函数的功能是什么,他要完成什么样的一件事,而这个,是完全由你自己来定义的。也就是说,我们先不管函数里面的代码什么,而是要先明白,你这个函数是要用来干什么。

例如,我定义了一个函数

// 算 n 的阶乘(假设n不为0)
int f(int n){

}

这个函数的功能是算 n 的阶乘。好了,我们已经定义了一个函数,并且定义了它的功能是什么,接下来我们看第二要素。

第二要素:寻找递归结束条件

所谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出递归的结束条件,不然的话,会一直调用自己,进入无底洞。也就是说,我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么。

例如,上面那个例子,当 n = 1 时,那你应该能够直接知道 f(n) 是啥吧?此时,f(1) = 1。完善我们函数内部的代码,把第二要素加进代码里面,如下

// 算 n 的阶乘(假设n不为0)
int f(int n){
    if(n == 1){
        return 1;
    }
}

有人可能会说,当 n = 2 时,那我们可以直接知道 f(n) 等于多少啊,那我可以把 n = 2 作为递归的结束条件吗?

当然可以,只要你觉得参数是什么时,你能够直接知道函数的结果,那么你就可以把这个参数作为结束的条件,所以下面这段代码也是可以的。

// 算 n 的阶乘(假设n>=2)
int f(int n){
    if(n == 2){
        return 2;
    }
}

注意我代码里面写的注释,假设 n >= 2,因为如果 n = 1时,会被漏掉,当 n <= 2时,f(n) = n,所以为了更加严谨,我们可以写成这样:

// 算 n 的阶乘(假设n不为0)
int f(int n){
    if(n <= 2){
        return n;
    }
}

第三要素:找出函数的等价关系式

第三要素就是,我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。

例如,f(n) 这个范围比较大,我们可以让 f(n) = n * f(n-1)。这样,范围就由 n 变成了 n-1 了,范围变小了,并且为了原函数f(n) 不变,我们需要让 f(n-1) 乘以 n。

说白了,就是要找到原函数的一个等价关系式,f(n) 的等价关系式为 n * f(n-1),即

f(n) = n * f(n-1)。

找出了这个等价,继续完善我们的代码,我们把这个等价式写进函数里。如下:

// 算 n 的阶乘(假设n不为0)
int f(int n){
    if(n <= 2){
        return n;
    }
    // 把 f(n) 的等价操作写进去
    return f(n-1) * n;
}

至此,递归三要素已经都写进代码里了,所以这个 f(n) 功能的内部代码我们已经写好了。

例子

//斐波那契数列的是这样一个数列:1、1、2、3、5、8、13、21、34....,
// 即第一项 f(1) = 1,第二项 f(2) = 1.....,第 n 项目为 f(n) = f(n-1) + f(n-2)。
// 求第 n 项的值是多少。
int fibo(int n){
    if(n<=2){
        return 1;
    }
    return fibo(n-1)+fibo(n-2);
}
int main(){
    cout<<fibo(8);
    return 0;
}21
//一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
int gua(int n){
    if(n<=2){
        return n;
    }
    return gua(n-1)+gua(n-2);//第一次跳一级还剩n-1级,跳两级还剩n-2级
}
int main(){
    cout<<gua(9);
    return 0;
}55

函数指针

#include <iostream>
double betsy(int);
double pam(int);
void estimate(int lines, double (*pf)(int));//传参时特征标和返回值都应匹配
// 第二个参数是一个函数指针,它指向参数为int、返回值为double的指针
int main()
{
    using namespace std;
    int code;
    cout << "How many lines of code do you need? ";
    cin >> code;
    cout << "Here's Betsy's estimate:\n";
    estimate(code, betsy);
    cout << "Here's Pam's estimate:\n";
    estimate(code, pam);
    // cin.get();
    // cin.get();
    return 0;
}//函数指针使得一个函数可以调用不同的函数
//直接在函数中调用函数则做不到这一点 
double betsy(int lns)
{
    return 0.05 * lns;
}//两种函数对应两种“算法”
//使用函数指针可以灵活的任意调用
double pam(int lns)
{
    return 0.03 * lns + 0.0004 * lns * lns;
}
//函数名就是函数指针,声明指针的格式:typeName (*pointName)(argumentType)
void estimate(int lines, double (*pf)(int))
{
    using namespace std;
    cout << lines << " lines will take ";
    cout << (*pf)(lines) << " hour(s)\n";
}//(*pf)可以是任意满足类型的函数,而不是确定的函数

How many lines of code do you need?30
 Here's Betsy's estimate:
30 lines will take 1.5 hour(s)
Here's Pam's estimate:
30 lines will take 1.26 hour(s)
const double* f1(const double ar[],int n);
const double* f2(const double [],int);//返回值为指向常量的指针,参数列表为常量数组和int
const double* f3(const double *,int);
//需要注意的是,f1、f2、f3的特征标看似不同,但实际上相同
//接下来声明一个指针,它可指向这三个函数之一,假设给指针名为pa
const double* (*pl)(const double*,int);
const double* (*pl)(const double*,int) = f1;//可以在声明的同时初始化
auto p2 = f2;//用auto将更加方便

typedef

int main(){
    typedef double real;
    real a=3.5;
    typedef const double* (*p_fun)(const double*,int);
    //*p_fun = f1错误用法
    p_fun f1;//正确用法
    typedef int* test;
    test b;//正确用法
    //*test b;错误用法
}

第五章 函数探幽

内联函数

常规函数和内联函数之间的主要区别不在于便携方式,而在于C++编译器如何将他们组合到程序中

用通俗的语言来讲,普通函数在程序调用函数时,会跳到函数内存块,再跳回来,

而对于内联函数,编译器将使用相应的函数代码替换函数调用,

内联函数的运行速度比常规函数稍快,但需要占用更多内存。

#include <iostream>
using namespace std;
//使用inline函数,必须在函数声明和函数定义前加上关键字inline
inline double square(double x)
{
    return x * x;
}//这里直接在main之前定义,就不用再声明了

int main()
{
    double a, b;
    double c = 13.0;
    a = square(5.0);
    b = square(4.5 + 7.5);   //可以用表达式做参数
    cout << "a = " << a << ", b = " << b << "\n";
    cout << "c = " << c;
    cout << ", c squared = " << square(c++) << "\n";
    cout << "Now c = " << c << "\n";
    return 0;  
}
a = 25, b = 144
c = 13, c squared = 169
Now c = 14

引用变量

C++新增了一种复合类型——引用变量,引用是已定义的变量的别名,它最主要的用途是用作函数的形参,

通过将引用变量作为参数,函数将使用原始数据,而不是其副本。

//C和C++使用&来指示变量的地址,C++给&符号赋予了另一个含义
int rats;
int& rodents = rats;//使rodents成为rats的另一个名字
//在上句中,&不是地址运算符,而是类型标识符的一部分
//就像char*表示指向char的指针一样,int&是指向int的引用
//上述引用声明允许将rats和rodents互换,它们指向相同的值和内存单元
int rats = 101;
int& rodents = rats;
int* prats = &rats;
//引用看上去很像(*指针),但实际上,引用还是不同于指针的,除了表示方法不同外,还有其他差别
//比如,引用不能像指针那样,先声明、再赋值,必须在声明引用变量时同时初始化
//引用更接近const指针,必须在创建时初始化,一旦与某个变量关联起来,就将一直效忠于它。
int& rodents = rats;
int* const pr = &rats;
//二者类似,其中引用rodents扮演的角色与表达式*pr相同
//引用变量的值一旦确定,就不会更改
int rats = 101;
int* pt = &rats;
int& rodents = *pt;//rodents在此处初始化
int bunnies = 50;
pt = &bunnies;//将pt改变,而rodents似乎会随着pt改变(毕竟初始化的时候,rodents是*pt的别名)
//但是,rodents并不会被改变,因为它效忠的永远是那一刻的*pt,也就是rats
//哪怕*pt在未来变了,它也不会改变

将引用作为函数参数

引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名

这种做法并没有绕过引用不能先定义再赋值的设定,因为函数直到被调用,它里面的变量才会被定义

void swapr(int& a, int& b)
{
    int temp;
    temp = a;	a = b;	b = temp; 
}
void swapp(int* p, int* q)
{
    int temp;
    temp = *p;	*p = *q;  *q = temp;
}
void swapv(int a, int b)
{
    int temp;
    temp = a;	a = b;	b = temp;
}
int main()
{
    using namespace std;
    int wallet1 = 300;	int wallet2 = 350;
    
    cout << "wallet1 = $" << wallet1<< " wallet2 = $" << wallet2 << endl;

    cout << "Using references to swap contents:\n";
    swapr(wallet1, wallet2);//参数传递实现了int& a=wallet1 和 int& b=wallet2
    cout << "wallet1 = $" << wallet1<< " wallet2 = $" << wallet2 << endl;

    cout << "Using pointers to swap contents again:\n";
    swapp(&wallet1, &wallet2);//参数传递实现了int* a=&wallet1 和 int* b=&wallet2
    cout << "wallet1 = $" << wallet1<< " wallet2 = $" << wallet2 << endl;

    cout << "Trying to use passing by value:\n";
    swapv(wallet1, wallet2);//参数传递实现了int a=wallet1 和 int b=wallet2
    cout << "wallet1 = $" << wallet1<< " wallet2 = $" << wallet2 << endl;
    return 0;
}

wallet1 = $300 wallet2 = $350
Using references to swap contents:
wallet1 = $350 wallet2 = $300//使用引用互换,成功
Using pointers to swap contents again:
wallet1 = $300 wallet2 = $350//使用指针互换,成功(又换回来了)
Trying to use passing by value:
wallet1 = $300 wallet2 = $350//使用副本互换(值传递),失败(没变)

注意事项

避免返回函数终止时不再存在的内存单元引用

const int& clone(int& ft)//本代码块为错误示范
{
    int a;
    a=ft;
    return a;//把ft的值给了a,并且返回a
}//该函数返回了一个临时变量的引用
const string& version3(string& s1,const string& s2)
{
    string temp;
    temp = s2+s1+s2;
    return temp;//返回了临时变量的引用
}
string version1(const string & s1, const string & s2)
{
    string temp;
    temp = s2 + s1 + s2;
    return temp;//返回类型为string,temp将被复制到一个临时存储单元
}

const string & version2(string & s1, const string & s2)   // has side effect
{
    s1 = s2 + s1 + s2;
    return s1; //返回了已有的引用
}

const string & version3(string & s1, const string & s2)   // bad design
{
    string temp;
    temp = s2 + s1 + s2;
    return temp;//返回了临时变量的引用,错误示范
}
 
int main()
{
    string input;
    string copy;
    string result;

    cout << "Enter a string: ";
    getline(cin, input);
    copy = input;
    cout << "Your string as entered: " << input << endl;
    result = version1(input, "***");
    cout << "Your string enhanced: " << result << endl;
    cout << "Your original string: " << input << endl;
 
    result = version2(input, "###");
    cout << "Your string enhanced: " << result << endl;
    cout << "Your original string: " << input << endl;

    cout << "Resetting original string.\n";
    input = copy;
    result = version3(input, "@@@");
    cout << "Your string enhanced: " << result << endl;
    cout << "Your original string: " << input << endl;
    return 0;
}
Enter a string:sdfaf
Your string as entered: sdfaf
Your string enhanced: ***sdfaf***
Your original string: sdfaf
Your string enhanced: ###sdfaf###
Your original string: ###sdfaf###//version2直接用了input的引用,导致input也随之变化
Resetting original string.

Process finished with exit code -1073741819 (0xC0000005)//version3报错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值