C++ primer plus学习笔记(一)——基础语法

绝地灬酷狼 2023-07-18 09:43 90阅读 0赞

C++ primer plus学习笔记(一)——基础语法

    • 引言
    • 指针与&、*
    • 宏定义与内联函数
    • 类型别名
    • 泛型
    • decltype
    • const与mutable
      • const
      • mutable
    • volatile
    • thread_local
    • register
    • auto
    • static与extern
    • 枚举
    • new与delete
      • 使用new申请内存
      • 使用delete释放内存
    • 命名空间
    • 文件IO
    • array和vector初探

引言

本来是java方向的程序员,最近开始学习cpp,刚看到C++ primer plus这本书的第10章,在进入cpp的面向对象学习前,先整理下前面9章的知识笔记,以下内容基于c++11标准

指针与&、*

&在c++中的一种作用为取出当前变量在内存的逻辑地址
*在c++中的一种作用为取出当前逻辑地址对应的内存空间的值
c++中提供指针变量存储对象的地址,指针的运算会被编译器优化为地址的运算,比如一个int类型的指针 p+1的值实际是p指向的地址+1个int所占空间大小后的逻辑地址
指针语法为 typename * variable,比如

  1. int a = 5;
  2. int * p = &a;
  3. *p == 5;//true

而对于结构体指针,可以使用->来操作成员,如

  1. struct data{
  2. int a = 1;
  3. }
  4. data d1 = {
  5. a:2
  6. };
  7. data * dp1 = &d1;
  8. d1.a = 3;
  9. dp1->a = 5;
  10. (*dp1).a = 6;

宏定义与内联函数

c++中可以在文件头使用#define进行宏定义,编译器在编译时会将代码里特定字符串替换为宏定义以后的结果,这也是内联函数的实现原理(第6版书中255页,8.1小节),所以内联函数会比较占据内存(多个代码副本),也就是调用内联函数的地方,会被编译器替换为内联函数的执行代码,与宏定义的函数区别是,内联函数更加的严谨,其限定了参数类型及返回值类型
比如

  1. #define eetal 1
  2. int a = eetal;//compile as int a = 1;
  3. #define sum(x,y) (x+y)
  4. int b = sum(1,2);//compile as int b = (1+2)
  5. inline int sum(int a,int b){
  6. return a+b;
  7. }

因为宏定义会对文本替换,一般用于定义常量等,为了避免重复宏定义,c++提供了#ifndef(if not define缩写)命令来判断当前是否进行了某个名称的宏定义,可以根据结果进行处理
如:

  1. #ifndef eetal
  2. //code
  3. #endif

同时还有用于判断宏定义的#if

  1. #define a 5
  2. #if a>5
  3. ...
  4. #endif

以上代码代表如果没有定义过eetal这个宏变量,则会执行#ifndef和最近的endif之间的代码

类型别名

与宏定义类似的,c++还提供了typedef可以对类型取别名和定义一些函数指针的别名
比如

  1. typedef int iint;
  2. typedef void (*functionPointer)(int a);//接下来代码中

在上述代码之后的代码中使用iint定义变量等价于使用int
使用functionPointer作为类型可以定义指向 返回值为void,只有一个int形参的函数指针
c++11标准给using指令也加入了取别名的用法(真希望可以废弃一些旧的用法,太多重复的东西)。上述代码等价于

  1. using iint = int;
  2. using functionPointer = void (*)(int a);

泛型

c++的泛型通过template来设定,泛型方法代表该方法尚未注册实际代码,只有在代码里调用了该方法时,会通过隐式触发或者显示定义或者主动触发来创建对应的方法实例
方法的匹配规则为 代码中指定泛型方法>普通方法>显示声明>泛型方法

  1. //在一些标准好像typename也会写为class,如下的两种写法swap01和swap02一样
  2. template<class T> void swap02(T &a,T &b){
  3. T temp = b;
  4. b = a;
  5. a = temp;
  6. }
  7. template<typename T> void swap01(T &a,T &b){
  8. T temp = b;
  9. b = a;
  10. a = temp;
  11. }
  12. //泛型的显示声明,匹配规则(代码中指定泛型方法>普通方法>显示声明>泛型方法)
  13. template<> void swap01<int>(int &a,int &b);

此时调用如

  1. int a,b;
  2. swap01(a,b);
  3. //普通调用,先匹配显示声明
  4. double c,d;
  5. swap01(c,d);
  6. //显示声明不匹配类型,触发隐式生成double泛型的方法实例匹配
  7. char e,f;
  8. swap01<char>(e,f);
  9. //主动指定方法泛型为char

decltype

因为泛型会导致方法的不安全,比如

  1. template<typename T> void sum(T &a,T &b){
  2. ? c = a+b;
  3. //do something
  4. }

因为c++重载了运算符,string类型也可以使用+拼接,那如何确定泛型变量运算返回值的类型呢?于是有了decltype

  1. decltype(a+b) c = a+b;

上述代码代表,如果a+b的表达式合法,c的类型即为他们运算后值的类型,deltype还有一个特殊用法来创建引用

  1. int a;
  2. decltype((a)) sa = a;//equals to int & sa = a;

上述代码代表sa的类型为 a的类型 的引用类型,即变量sa此时是a的一个引用

const与mutable

const

const代表常量定义(java里的final),不可修改。因为cpp里面的指针类型定义比较特殊,所以const的位置代表不同含义,如

  1. const int v = 0;//v cantnot be change anymore
  2. struct data{
  3. int a;
  4. };
  5. ....
  6. data t1 = {
  7. a = 0;
  8. },t2 = {
  9. a = 3;
  10. };
  11. const data * p1 = &t1;
  12. //p1->a = 4 cannot work, error
  13. //p1 = p2 it work
  14. data * const p2 = &t2;
  15. //p2->a = 4 it work
  16. //p2 = p1 cannot work, error

如上述代码,p1可以修改指向的地址,但是无法通过p1修改单元内容
p2不可修改指向的地址,但是可以通过p2修改单元内容

mutable

mutable用于标记结构体中不想受结构体的const限制的成员,比如

  1. const struct data{
  2. int a = 1;
  3. mutable int b = 0;
  4. }
  5. data * d = new data();
  6. d->b = 3;//ok
  7. //d->a = 5;error

上述代码创建了一个结构体data,因为其以const修饰,成员a不能被修改,而因为成员b以mutable修饰,不受限制可以修改

volatile

与java一样消除内存屏障用的,标记告诉编译器不要缓存该变量到寄存器或者线程缓存,每次从内存读取

  1. volatile int a;

thread_local

用于创建存放在当前线程作用域的对象,该对象存放在当前线程内存,在当前线程存活时间里保持存活

  1. thread_local int a = 1;

register

在c++11标准里代表默认,类似java的default(在以前标准为通知编译器尽量存储到寄存器,不过c++11改了)

auto

在c++11标准代表类型推断,必须在定义时为变量完成初始化,否则无法推断(在旧版本标准为默认,即当前的register作用)

  1. auto a = 1;
  2. //auto b; error

static与extern

在c++中,在文件的函数外部,及全局便写的变量为全局变量,其他连接的工程文件在代码里可以通过extern关键字引入其他文件的全局变量。
而static代表标记变量为静态的全局变量,不能被extern发现
全局变量的生命周期时整个程序运行期间

  1. //1.cpp
  2. int A = 100;
  3. static int B = 5;
  4. //2.cpp
  5. extern int A;//100
  6. //extern int B; error

枚举

c++的枚举存储整数,不指定默认第一个为0后续每个依次递增,整数不能直接赋值给枚举变量,需要通过构造,而枚举变量可以直接赋值给整数

  1. enum langs{ java, cpp};// 0 and 1
  2. //enum langs{ java=3, cpp=100};// 3 and 100
  3. langs newLang = langs(1);//cpp
  4. langs anotherLang = java;
  5. int howMuch = anotherLang;//0

new与delete

cpp中也是使用new来创建对象(在cpp中比较习惯称为分配内存,c里还有malloc,calloc,realloc),同时提供释放内存的指令,这会造成很多危险,但是也带来性能的提升

使用new申请内存

  1. #include<new>
  2. int * p = new int();//只申请一个int的空间,返回地址
  3. int * ap = new int[5];//申请一个5个int元素的数值空间,返回第一个元素地址
  4. int * p2 = new(p) int();//指定要分配空间的首地址,此种方式要引入new头文件

如上代码,其中ap为数组头,而p2指定了首地址为p1,这样代表其分配的空间覆盖了p1的空间,当尝试申请的内存不够时,在c++11中会抛出异常(以前是返回空地址0)

使用delete释放内存

对于普通指针,使用delete 指针删除,而对于数组类型的指针,应当使用delete[]来调用数组各个元素的析构函数(基本类型只是释放),因为p1已经被p2覆盖,所以释放了p2就不能再去释放p1,因为内存已经被回收了

  1. delete p2;
  2. delete[] ap;

命名空间

c++因为大部分代码还是存在面向过程,放在全局的变量的做法,为了方便管理避免重名,引入命名空间,通过命名空间归类方法和变量,比如

  1. namespace std{
  2. istream cin;
  3. ostream cout;
  4. }

上述代码创建了一个std的命名空间,里面的对象通过 命名空间::成员名称 使用,比如

  1. std::cin;
  2. std::cout;

通过使用using命令,指定将命名空间内容加入当前代码块,则可以省略命名空间,如

  1. using namespace std;
  2. cin;
  3. cout;

命名空间还可以嵌套

  1. namespace yyt{
  2. namespace std{
  3. istream cin;
  4. ostream cout;
  5. }
  6. }
  7. ....
  8. yyt::std::cin;

对于本文件的全局变量,可以使用匿名命名空间访问成员,如

  1. #include<iostream>
  2. namespace eetal{
  3. int a = 10;
  4. }
  5. int a = 5;
  6. int main(){
  7. int a = 3;
  8. std::cout<<a<<endl;//3
  9. std::cout<<::a<<endl;//5
  10. std::cout<<eetal::a<<endl;//10
  11. return 0;
  12. }

文件IO

文件IO需要通过引入头文件fstream

  1. #inlcude<fstream>
  2. //输出流ofstream
  3. ofstream outPutStream;
  4. outPutStream.open("test.txt");//will clear old content
  5. outPutStream.write("aaas",4);//writer max 4 characters
  6. outPutStream.flush();
  7. outPutStream.close();
  8. //输入流ifstream,
  9. ifstream inputStream;
  10. inputStream.open("test.txt");
  11. char str[5];
  12. inputStream.read(str,4);//read max 4 characters
  13. inputStream.close();

array和vector初探

前10章简单讲了下array是有界的,vactor无界(自动增长),并且可以直接像数组一样使用
创建语法

  1. #include<array>
  2. #include<vector>
  3. int size = 5;
  4. vector<double> doublevWithSize(size);//size is a variable
  5. array<int,5> array1;//cannot use variable the size must is const
  6. doublevWithSize[0] = array1[0];

c++类都不大写开头,源码暂时看着很难懂,了解多点后再分析吧

更多文章,请搜索公众号歪歪梯Club
更多资料,请搜索公众号编程宝可梦

发表评论

表情:
评论列表 (有 0 条评论,90人围观)

还没有评论,来说两句吧...

相关阅读