C++基础知识(1)基本特性

1. 程序的执行过程

  • 代码区与常量区(只读区):真正的常量存储在这里,比如“abc”,"88"等字符串和数字。而const关键字只是让编译器把变量视为常量罢了,和真正的常量有本质区别。
  • 栈区:函数执行所需的空间,当函数执行完毕,则对应的占内存将全部销毁。
  • 堆区:进程用来灵活分配内存的地方,需要栈区指针来指向。
  • 静态变量区(可读写区):用来存储静态变量和全局变量的地方。

2. new关键字

new关键字是C++用来动态分配内存的主要方式。

/*
* 动态分配对象
* 对于类,会使用对应构造函数,没有对应构造函数会报错
**/
int* pI = new int; // 不初始化,pI是未定义的,不知道指向哪里
int* pI = new int(100); // 栈中保存pI指针,指向堆区的内存,栈可以调用pI来操作堆区的对应内存
delete(pI); // 使用了new的对象,一定要delete
/*
* 动态分配数组
* 对于类数组,有没有()都一样,均使用默认构造函数,如果没有默认函数会报错
**/
int* pI = new int[50]// 没有()即不初始化,pI是未定义的,不知道指向哪里
int* pI = new int[50](); // 使用[]初始化数组,有()则全初始化为0
delete[] pI; // 释放数组,需要加[]
std::string* pString = new std::string[50]("111"); // 错误,只能使用默认构造函数

内存泄漏会出现很严重的问题!!!

3. 命名空间

团队合作时会常常出现起名重复的问题,命名空间就是来解决这个问题的。

/*
* A文件
**/
#include<iostream>
void test()
{
    std::cout << "A_test()" << std::endl;
}
/*
* B文件
**/
#include<iostream>
void test()
{
    std::cout << "B_test()" << std::endl;
}
/*
* main()函数
**/
#include"ATest.h"
#include"BTest.h"
int main()
{
 test(); // 此时系统不知道调用哪个test()
}

使用命名空间很容易就解决上述问题。

/*
* A文件
**/
#include<iostream>
namespace A // 命名空间A
{
    void test()
    {
        std::cout << "A_test()" << std::endl;
    }
}
/*
* B文件
**/
#include<iostream>
namespace B // 命名空间B
{
    void test()
    {
        std::cout << "B_test()" << std::endl;
    }
}
/*
* main()函数
**/
#include"ATest.h"
#include"BTest.h"
using A::test; // 法一:使用using明确需要的函数
using namespace std; // 法二:使用using namespace明确需要的命名空间
int main()
{
    A::test(); // 法三:调用A命名空间的函数
    std::cout; // 所有C没有的函数,而C++有的都定义在std的命名空间中
}

注意:头文件中一定不要使用using关键字,不然会导致命名空间的污染。

4. const关键字

规则:const默认与左边结合,左边没有东西则与右边结合。

/*
* 常量数值:指向的值不能更改
* 常量指针:指针的指向不能更改
**/
const int* p; // const与int结合,即 p为指向常量数值的指针
int const* p; // const与int结合,即 p为指向常量数值的指针
int* const p; // const与*结合,即 p为指向变量数值的常量指针
const int* const p; // 第一个const与int结合,第二个与*结合,即 p为指向常量数值的常量指针

5. auto关键字

auto主要用于类型转换

int i = 100;
auto i2 = i; // auto自动推断出int,即i2为int类型
  1. auto只能推断出类型,引用不是类型,所以auto无法推断出引用,要使用引用只能自己加引用符号。
int i = 100;
auto& i2 = i; // i2类型为int&
  1. auto关键字在推断引用类型时,会直接将引用类型替换为引用指向的对象。其实引用就是个别名,我们在复制时,不管中间什么类型,都是一样的。
int i = 100;
const int& refI = i;
auto i2 = refI; // 相当于 auto i2=i
  1. auto关键字在推断类型时,会忽略值类型的const修饰,而保留修饰指向对象的const,即会保留指针,因为修改auto的变量不会对原来的值造成影响,因此不用保留。
/*整型*/
const int i = 100; // const修饰int,值类型的修饰被忽略
auto i2 = i; // i2的类型为int
/*指针*/
int i = 100;
const int* const pI = &i; // 第二个const修饰指针即为值类型的const,第一个const保留
auto pI2 = pI; // pI2的类型为const int*
  1. auto关键字在推断类型时,如果有了引用符号,那么值类型的const和修饰指向对象的const都会保留,引用必须保留const,不然原来的常量也可以修改了。
/*整型*/
const int i = 100;
auto& i2 = i; // i2的类型为const int&
 /*指针*/
int i = 100;
const int* const pI = &i; 
auto& pI2 = pI; // pI2的类型为const int* const&
  1. auto可以在前面加上const。
int i = 100;
const auto i2 = i; // i2类型为const int
  1. auto不会影响编译速度,甚至会加快编译速度。
XX A = B // 当XX为传统类型时,编译器会检查B的类型是否可以转换为XX
auto A = B // 编译器直接按照B的类型给A
  1. 如果不明确转换的类型,不要滥用auto。
  2. auto主要用于在模板相关的代码中。

6. 静态变量区

变量的存储位置:静态变量区、堆区、栈区
静态变量区在编译时就已经确定地址,存储全局变量和静态变量。

unsigned g_i = 0; // 全局变量,在编译的时候就初始化了
unsigned test()
{
    static unsigned callCount = 0; // 静态变量,在编译时就初始化了,运行时不执行这行代码		
    return ++callCount;
}
int testStatic()
{
    test();
    test();
    test();
    unsigned testFuncCallCount = test();
    return 0;
}

7. 指针与引用

指针都是存储在栈上或者堆上的,不管在栈上还是堆上,都一定有一个地址。
&a:a这个变量的地址
a:a这个变量的地址所存储的值
*a:以a的值为地址的地方所存储的值
指针能灵活操作内存,所以引用被发明了,引用就是阉割版的指针,即类型&===类型 * const。所以引用必须一开始就赋初值,因为常量必须赋初值,即引用相当于常量。

int i = 20;
int* p = &i; // 在内存上,i与p相邻存储,i存放20,p存放i的地址
/*下面两行代码一样*/
int& refI = i;
int* const pI = &i; 
// *pI与refI本质是一样的,都是指向i且不可改变
const int i; // 错误,常量必须赋初值
int& refI; // 错误,引用必须赋初值
refI = i;

8. 左值与右值

8.1 左右值的分类

左值:拥有地址属性的对象叫左值。左值可以放在=左边也可以放在右边。

int i = 10;
int i2 = i; // i为左值但可以放在右边

右值:不是左值的对象就是右值,没有地址属性的值就是右值。如临时对象都是右值。

int i = 10;
(i + 1) = 11; // (i + 1)是临时对象,它有地址,但没有我们可操作的地址属性
/*
* ++i的本质:++i为左值
**/
i = i + 1;
return i; // 返回的值就是i的地址存储的值
/*
* i++的本质:i++为右值
**/
temp = i; // 创建临时变量,对temp进行操作
temp += 1;
return temp; // 返回临时变量的值

判断左值右值:直接看代码的下一行,如果可以取到上一行的地址的对象,为左值,取不到为右值

8.2 引用的分类

  1. 普通左值引用,就是一个对象的别名,只能绑定左值,无法绑定常量对象。
const int i = 10;
int& refI = i; // 错误,refI可以修改,那i的const无意义
  1. const左值引用,可以绑定常量对象,可以绑定任何左值和右值。
const int i = 10;
const int& refI = (i + 1); // const引用绑定右值
  1. 右值引用,右值引用只能绑定右值。
int i = 100;
int&& rrefI = (i + 1); 
int&& rrefI = i; // 错误,右值引用无法绑定左值
  1. 万能引用

9. 右值引用的使用

9.1 move函数

  1. move函数可以对一个左值使用,使操作系统不考虑其地址属性,使其完全视为一个右值。
int i = 10;
int&& rrefI = i; // 错误
int&& rrefI = std::move(i); // 正确,使用move函数,可视作右值
  1. move函数让操作的对象失去了地址属性,所以我们之后不能在使用操作了move后的变量的地址属性,也就是以后都不能使用该变量了。

9.2 临时对象

临时对象都是右值,而右值引用主要负责处理临时对象,程序执行时生成的中间对象就是临时对象,临时对象产生后很快就会被销毁。

10. 可调用对象

如果一个对象可以使用运算符 “()” ,()里面可以放参数,那么这个对象就是可调用对象。

10.1 函数

#include<iostream>
void test(int i)
{
    std::cout << i << std::endl;
}
using pf_type = void(*) (int); // 定义函数test()的函数指针,函数指针与普通指针一样,只是多了个(参数)
void myFunc(pf_type pf, int i)
{
    pf(i);
}
void mian()
{
    myFunc(test, 200); // 可调用对象,myFunc为对象,()为运算符
}

10.2 仿函数

具有operator()的函数的类对象,此时类对象可以当做函数使用,称为仿函数。

10.3 lambda表达式

就是匿名函数,普通的函数在使用前需要将这个函数定义,因此提供了lambda表达式,省去了定义函数的过程。
lambda表达式的格式:最少是“[] {}”,完整的格式是“[] () -> ret{}”

void mian()
{
    [] {
        std::cout << "Hello" << std::endl;
    }();
}

lambda各个组件介绍:

  1. [] 代表捕获列表,表示lambda表达式可以访问前文的哪些变量。
    (1)[] 表示不捕获任何变量。
    (2)[=] 表示按值捕获所有变量。
    (3)[&] 表示按引用捕获所有变量。值和引用的区别与函数传递一致。
    (4)[=, &i] 表示i用引用传递,其它所有变量均用值传递。
    (5)[=, i] 与(4)的作用一致,表示i用引用传递,其它所有变量均用值传递。
    (6)[i] 表示用值的形式单独的捕获i。
    (7)[&i] 表示用引用的形式单独的捕获i。
  2. () 代表lambda表达式的参数,与函数类似。
[=] (int i) // 函数的参数
{
    std::cout << "Hello" << std::endl;
}(10); // 函数的传值
  1. ->ret 表示指定lambda的返回值,如果不指定,lambda表达式也会推断出一个返回值。
auto ret = [=] (int i) -> int // -> 规定返回类型,默认为void
{
    std::cout << "Hello" << std::endl;
    return i; // return返回值
}(10);
  1. {} 代表函数体,与普通函数完全一致。
#include<iostream>
using pf_type = void(*) (int); // 定义函数test()的函数指针类型
void myFunc(pf_type pf, int i)
{	
    pf(i);
}
void mian()
{
    myFunc([](int i) { // lambda表达式作为函数指针参数传递,其[]必须为空
         std::cout << "Hello" << std::endl;
         }, 30);
}
#include<iostream>
#include<functional>
using func_typr = std::function<void(int)>;
void myFunc(func_typr func, int i)
{
    func(i);
}
void mian()
{
    int i2 = 50;
    myFunc([i2](int i) { // 使用std::fuction可以解决作为函数指针[]必须为空的问题
         std::cout << "Hello" << std::endl;
         } , 30);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在C编程语言基础知识面试中,通常会涉及以下几个方面的问题: 1. C语言的特点与用途:C语言是一种通用的高级程序设计语言,可以用来开发底层操作系统、嵌入式系统、游戏开发等应用。它具有简洁、效率高、可移植性强等特点。 2. C语言的数据类型与变量:C语言支持不同的数据类型,包括整型、浮点型、字符型等。使用变量来存储数据,变量需要先声明后使用,并且可以进行各种运算。 3. C语言的控制流程:C语言中的控制流程有条件判断语句、循环语句和跳转语句。条件判断语句通过判断条件的真假来决定执行哪个分支;循环语句可以重复执行一段代码,直到满足退出条件;跳转语句可以改变代码的执行顺序。 4. C语言的函数与库:C语言支持函数的定义和调用,可以将一段代码封装成函数,提高代码的复用性。此外,C语言还提供了一些标准库,包括输入输出库、数学库等,可以在程序中使用这些库函数来完成各种操作。 5. C语言的指针和内存管理:C语言具有灵活的指针操作功能,可以通过指针来直接访问内存中的数据。指针可以用于动态内存分配和释放,通过调用malloc()和free()函数来进行操作。 除了以上几个方面的基础知识,面试中可能还会涉及到关于C语言的编程题,例如要求解决某个具体的问题或者实现某个算法等。在面试中,不仅要掌握C语言基础知识,还需要具备解决问题的思路和能力,能够独立分析和编写C程序。 ### 回答2: C基础知识面试主要涉及面向过程的编程语言C的基础概念、语法和应用。在面试中,通常会涉及以下几个方面的问题: 1. C语言基本概念和特性:要求候选人能够解释C语言的起源、用途和特点,了解C语言的运行环境和编译过程。 2. C语言的数据类型:面试官可能会问到C语言中的基本数据类型,如int、char、float等,以及它们在内存中的存储方式和占用空间大小。 3. C语言的控制结构:掌握C语言中的分支语句(if-else、switch-case)和循环语句(for、while、do-while),并能够解答相关的应用题。 4. C语言的函数:了解函数的定义、声明和调用规则,理解函数的参数传递和返回值机制,能够编写简单的函数。 5. C语言的指针:熟悉指针的基本概念和用法,了解指针和数组、指针和函数之间的关系,能够解决指针相关的问题。 6. C语言的内存管理:了解动态内存分配(malloc、free)和静态内存分配(全局变量、局部变量)的区别和用法,了解内存泄漏和内存溢出的概念。 7. C语言的文件操作:熟悉文件的打开、读写、关闭等基本操作,能够读写文本文件和二进制文件,了解文件指针的概念和用法。 8. C语言的预处理器:了解预处理器的作用和常用指令(如#define、#include等),理解宏定义的概念和用法。 在C基础知识面试中,除了回答问题,面试官可能还会要求候选人编写简单的C代码,以检测其编程能力和解决问题的能力。所以,在准备面试时,应该复习C语言的基础概念、语法和应用,并进行代码练习,提高自己的实践能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值